mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Merge pull request #1052 from wger-project/feature/trophies
Feature/trophies
This commit is contained in:
@@ -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) 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
|
||||
@@ -70,6 +70,14 @@ DateTime utcIso8601ToLocalDate(String dateTime) {
|
||||
return DateTime.parse(dateTime).toLocal();
|
||||
}
|
||||
|
||||
DateTime? utcIso8601ToLocalDateNull(String? dateTime) {
|
||||
if (dateTime == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return utcIso8601ToLocalDate(dateTime);
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a time to a date object.
|
||||
* Needed e.g. when the wger api only sends a time but no date information.
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
"slotEntryTypeTut": "Time under Tension",
|
||||
"slotEntryTypeIso": "Isometric hold",
|
||||
"slotEntryTypeJump": "Jump",
|
||||
"trophies": "Trophies",
|
||||
"routines": "Routines",
|
||||
"newRoutine": "New routine",
|
||||
"noRoutines": "You have no routines",
|
||||
@@ -233,6 +234,16 @@
|
||||
"@comment": {
|
||||
"description": "Comment, additional information"
|
||||
},
|
||||
"topSet": "Top set: {value}",
|
||||
"@topSet": {
|
||||
"description": "Value is a representation of the set, like '10 x 80kg'",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"impressionGood": "Good",
|
||||
"impressionNeutral": "Neutral",
|
||||
"impressionBad": "Bad",
|
||||
@@ -263,6 +274,7 @@
|
||||
},
|
||||
"selectExercises": "If you want to do a superset you can search for several exercises, they will be grouped together",
|
||||
"@selectExercises": {},
|
||||
"personalRecords": "Personal records",
|
||||
"gymMode": "Gym mode",
|
||||
"@gymMode": {
|
||||
"description": "Label when starting the gym mode"
|
||||
@@ -591,6 +603,7 @@
|
||||
"@newEntry": {
|
||||
"description": "Title when adding a new entry such as a weight or log entry"
|
||||
},
|
||||
"noTrophies": "You have no trophies yet",
|
||||
"noWeightEntries": "You have no weight entries",
|
||||
"@noWeightEntries": {
|
||||
"description": "Message shown when the user has no logged weight entries"
|
||||
|
||||
105
lib/main.dart
105
lib/main.dart
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 wger Team
|
||||
* Copyright (c) 2020 - 2026 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
|
||||
@@ -35,6 +35,7 @@ import 'package:wger/providers/measurement.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/providers/wger_base_riverpod.dart';
|
||||
import 'package:wger/screens/add_exercise_screen.dart';
|
||||
import 'package:wger/screens/auth_screen.dart';
|
||||
import 'package:wger/screens/dashboard.dart';
|
||||
@@ -58,6 +59,7 @@ import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/screens/settings_dashboard_widgets_screen.dart';
|
||||
import 'package:wger/screens/settings_plates_screen.dart';
|
||||
import 'package:wger/screens/splash_screen.dart';
|
||||
import 'package:wger/screens/trophy_screen.dart';
|
||||
import 'package:wger/screens/update_app_screen.dart';
|
||||
import 'package:wger/screens/weight_screen.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
@@ -130,7 +132,7 @@ void main() async {
|
||||
};
|
||||
|
||||
// Application
|
||||
runApp(const riverpod.ProviderScope(child: MainApp()));
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
@@ -218,48 +220,63 @@ class MainApp extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
child: Consumer<AuthProvider>(
|
||||
builder: (ctx, auth, _) => Consumer<UserProvider>(
|
||||
builder: (ctx, user, _) => MaterialApp(
|
||||
title: 'wger',
|
||||
navigatorKey: navigatorKey,
|
||||
theme: wgerLightTheme,
|
||||
darkTheme: wgerDarkTheme,
|
||||
highContrastTheme: wgerLightThemeHc,
|
||||
highContrastDarkTheme: wgerDarkThemeHc,
|
||||
themeMode: user.themeMode,
|
||||
home: _getHomeScreen(auth),
|
||||
routes: {
|
||||
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
|
||||
FormScreen.routeName: (ctx) => const FormScreen(),
|
||||
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
|
||||
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
|
||||
HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(),
|
||||
MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(),
|
||||
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
|
||||
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
|
||||
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
|
||||
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
|
||||
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
|
||||
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
|
||||
WeightScreen.routeName: (ctx) => const WeightScreen(),
|
||||
RoutineScreen.routeName: (ctx) => const RoutineScreen(),
|
||||
RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(),
|
||||
WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(),
|
||||
RoutineListScreen.routeName: (ctx) => const RoutineListScreen(),
|
||||
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
|
||||
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
|
||||
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
|
||||
AboutPage.routeName: (ctx) => const AboutPage(),
|
||||
SettingsPage.routeName: (ctx) => const SettingsPage(),
|
||||
LogOverviewPage.routeName: (ctx) => const LogOverviewPage(),
|
||||
ConfigurePlatesScreen.routeName: (ctx) => const ConfigurePlatesScreen(),
|
||||
ConfigureDashboardWidgetsScreen.routeName: (ctx) =>
|
||||
const ConfigureDashboardWidgetsScreen(),
|
||||
},
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
),
|
||||
),
|
||||
builder: (ctx, auth, _) {
|
||||
final baseInstance = WgerBaseProvider(Provider.of(ctx, listen: false));
|
||||
|
||||
return Consumer<UserProvider>(
|
||||
builder: (ctx, user, _) => riverpod.ProviderScope(
|
||||
overrides: [
|
||||
wgerBaseProvider.overrideWithValue(baseInstance),
|
||||
],
|
||||
child: riverpod.Consumer(
|
||||
builder: (rpCtx, ref, _) {
|
||||
return MaterialApp(
|
||||
title: 'wger',
|
||||
navigatorKey: navigatorKey,
|
||||
theme: wgerLightTheme,
|
||||
darkTheme: wgerDarkTheme,
|
||||
highContrastTheme: wgerLightThemeHc,
|
||||
highContrastDarkTheme: wgerDarkThemeHc,
|
||||
themeMode: user.themeMode,
|
||||
home: _getHomeScreen(auth),
|
||||
routes: {
|
||||
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
|
||||
FormScreen.routeName: (ctx) => const FormScreen(),
|
||||
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
|
||||
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
|
||||
HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(),
|
||||
MeasurementCategoriesScreen.routeName: (ctx) =>
|
||||
const MeasurementCategoriesScreen(),
|
||||
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
|
||||
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
|
||||
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
|
||||
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
|
||||
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
|
||||
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
|
||||
WeightScreen.routeName: (ctx) => const WeightScreen(),
|
||||
RoutineScreen.routeName: (ctx) => const RoutineScreen(),
|
||||
RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(),
|
||||
WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(),
|
||||
RoutineListScreen.routeName: (ctx) => const RoutineListScreen(),
|
||||
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
|
||||
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
|
||||
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
|
||||
AboutPage.routeName: (ctx) => const AboutPage(),
|
||||
SettingsPage.routeName: (ctx) => const SettingsPage(),
|
||||
LogOverviewPage.routeName: (ctx) => const LogOverviewPage(),
|
||||
ConfigurePlatesScreen.routeName: (ctx) => const ConfigurePlatesScreen(),
|
||||
ConfigureDashboardWidgetsScreen.routeName: (ctx) =>
|
||||
const ConfigureDashboardWidgetsScreen(),
|
||||
TrophyScreen.routeName: (ctx) => const TrophyScreen(),
|
||||
},
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
66
lib/models/trophies/trophy.dart
Normal file
66
lib/models/trophies/trophy.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'trophy.g.dart';
|
||||
|
||||
enum TrophyType { time, volume, count, sequence, date, pr, other }
|
||||
|
||||
@JsonSerializable()
|
||||
class Trophy {
|
||||
@JsonKey(required: true)
|
||||
final int id;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String uuid;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String name;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String description;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String image;
|
||||
|
||||
@JsonKey(required: true, name: 'trophy_type')
|
||||
final TrophyType type;
|
||||
|
||||
@JsonKey(required: true, name: 'is_hidden')
|
||||
final bool isHidden;
|
||||
|
||||
@JsonKey(required: true, name: 'is_progressive')
|
||||
final bool isProgressive;
|
||||
|
||||
Trophy({
|
||||
required this.id,
|
||||
required this.uuid,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.image,
|
||||
required this.type,
|
||||
required this.isHidden,
|
||||
required this.isProgressive,
|
||||
});
|
||||
|
||||
// Boilerplate
|
||||
factory Trophy.fromJson(Map<String, dynamic> json) => _$TrophyFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$TrophyToJson(this);
|
||||
}
|
||||
54
lib/models/trophies/trophy.g.dart
Normal file
54
lib/models/trophies/trophy.g.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'trophy.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Trophy _$TrophyFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const [
|
||||
'id',
|
||||
'uuid',
|
||||
'name',
|
||||
'description',
|
||||
'image',
|
||||
'trophy_type',
|
||||
'is_hidden',
|
||||
'is_progressive',
|
||||
],
|
||||
);
|
||||
return Trophy(
|
||||
id: (json['id'] as num).toInt(),
|
||||
uuid: json['uuid'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
image: json['image'] as String,
|
||||
type: $enumDecode(_$TrophyTypeEnumMap, json['trophy_type']),
|
||||
isHidden: json['is_hidden'] as bool,
|
||||
isProgressive: json['is_progressive'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$TrophyToJson(Trophy instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'uuid': instance.uuid,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'image': instance.image,
|
||||
'trophy_type': _$TrophyTypeEnumMap[instance.type]!,
|
||||
'is_hidden': instance.isHidden,
|
||||
'is_progressive': instance.isProgressive,
|
||||
};
|
||||
|
||||
const _$TrophyTypeEnumMap = {
|
||||
TrophyType.time: 'time',
|
||||
TrophyType.volume: 'volume',
|
||||
TrophyType.count: 'count',
|
||||
TrophyType.sequence: 'sequence',
|
||||
TrophyType.date: 'date',
|
||||
TrophyType.pr: 'pr',
|
||||
TrophyType.other: 'other',
|
||||
};
|
||||
62
lib/models/trophies/user_trophy.dart
Normal file
62
lib/models/trophies/user_trophy.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
|
||||
import 'trophy.dart';
|
||||
import 'user_trophy_context_data.dart';
|
||||
|
||||
part 'user_trophy.g.dart';
|
||||
|
||||
/// A trophy awarded to a user for achieving a specific milestone.
|
||||
|
||||
@JsonSerializable()
|
||||
class UserTrophy {
|
||||
@JsonKey(required: true)
|
||||
final int id;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final Trophy trophy;
|
||||
|
||||
@JsonKey(required: true, name: 'earned_at', fromJson: utcIso8601ToLocalDate)
|
||||
final DateTime earnedAt;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final num progress;
|
||||
|
||||
@JsonKey(required: true, name: 'is_notified')
|
||||
final bool isNotified;
|
||||
|
||||
@JsonKey(required: true, name: 'context_data')
|
||||
final ContextData? contextData;
|
||||
|
||||
UserTrophy({
|
||||
required this.id,
|
||||
required this.trophy,
|
||||
required this.earnedAt,
|
||||
required this.progress,
|
||||
required this.isNotified,
|
||||
this.contextData,
|
||||
});
|
||||
|
||||
// Boilerplate
|
||||
factory UserTrophy.fromJson(Map<String, dynamic> json) => _$UserTrophyFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserTrophyToJson(this);
|
||||
}
|
||||
40
lib/models/trophies/user_trophy.g.dart
Normal file
40
lib/models/trophies/user_trophy.g.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_trophy.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
UserTrophy _$UserTrophyFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const [
|
||||
'id',
|
||||
'trophy',
|
||||
'earned_at',
|
||||
'progress',
|
||||
'is_notified',
|
||||
'context_data',
|
||||
],
|
||||
);
|
||||
return UserTrophy(
|
||||
id: (json['id'] as num).toInt(),
|
||||
trophy: Trophy.fromJson(json['trophy'] as Map<String, dynamic>),
|
||||
earnedAt: utcIso8601ToLocalDate(json['earned_at'] as String),
|
||||
progress: json['progress'] as num,
|
||||
isNotified: json['is_notified'] as bool,
|
||||
contextData: json['context_data'] == null
|
||||
? null
|
||||
: ContextData.fromJson(json['context_data'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$UserTrophyToJson(UserTrophy instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'trophy': instance.trophy,
|
||||
'earned_at': instance.earnedAt.toIso8601String(),
|
||||
'progress': instance.progress,
|
||||
'is_notified': instance.isNotified,
|
||||
'context_data': instance.contextData,
|
||||
};
|
||||
75
lib/models/trophies/user_trophy_context_data.dart
Normal file
75
lib/models/trophies/user_trophy_context_data.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
|
||||
part 'user_trophy_context_data.g.dart';
|
||||
|
||||
/// A trophy awarded to a user for achieving a specific milestone.
|
||||
|
||||
@JsonSerializable()
|
||||
class ContextData {
|
||||
@JsonKey(required: true, name: 'log_id')
|
||||
final int logId;
|
||||
|
||||
@JsonKey(required: true, fromJson: utcIso8601ToLocalDate)
|
||||
final DateTime date;
|
||||
|
||||
@JsonKey(required: true, name: 'session_id')
|
||||
final int sessionId;
|
||||
|
||||
@JsonKey(required: true, name: 'exercise_id')
|
||||
final int exerciseId;
|
||||
|
||||
@JsonKey(required: true, name: 'repetitions_unit_id')
|
||||
final int repetitionsUnitId;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final num repetitions;
|
||||
|
||||
@JsonKey(required: true, name: 'weight_unit_id')
|
||||
final int weightUnitId;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final num weight;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final int? iteration;
|
||||
|
||||
@JsonKey(required: true, name: 'one_rep_max_estimate')
|
||||
final num oneRepMaxEstimate;
|
||||
|
||||
ContextData({
|
||||
required this.logId,
|
||||
required this.date,
|
||||
required this.sessionId,
|
||||
required this.exerciseId,
|
||||
required this.repetitionsUnitId,
|
||||
required this.repetitions,
|
||||
required this.weightUnitId,
|
||||
required this.weight,
|
||||
this.iteration,
|
||||
required this.oneRepMaxEstimate,
|
||||
});
|
||||
|
||||
// Boilerplate
|
||||
factory ContextData.fromJson(Map<String, dynamic> json) => _$ContextDataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ContextDataToJson(this);
|
||||
}
|
||||
50
lib/models/trophies/user_trophy_context_data.g.dart
Normal file
50
lib/models/trophies/user_trophy_context_data.g.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_trophy_context_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ContextData _$ContextDataFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const [
|
||||
'log_id',
|
||||
'date',
|
||||
'session_id',
|
||||
'exercise_id',
|
||||
'repetitions_unit_id',
|
||||
'repetitions',
|
||||
'weight_unit_id',
|
||||
'weight',
|
||||
'iteration',
|
||||
'one_rep_max_estimate',
|
||||
],
|
||||
);
|
||||
return ContextData(
|
||||
logId: (json['log_id'] as num).toInt(),
|
||||
date: utcIso8601ToLocalDate(json['date'] as String),
|
||||
sessionId: (json['session_id'] as num).toInt(),
|
||||
exerciseId: (json['exercise_id'] as num).toInt(),
|
||||
repetitionsUnitId: (json['repetitions_unit_id'] as num).toInt(),
|
||||
repetitions: json['repetitions'] as num,
|
||||
weightUnitId: (json['weight_unit_id'] as num).toInt(),
|
||||
weight: json['weight'] as num,
|
||||
iteration: (json['iteration'] as num?)?.toInt(),
|
||||
oneRepMaxEstimate: json['one_rep_max_estimate'] as num,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ContextDataToJson(ContextData instance) => <String, dynamic>{
|
||||
'log_id': instance.logId,
|
||||
'date': instance.date.toIso8601String(),
|
||||
'session_id': instance.sessionId,
|
||||
'exercise_id': instance.exerciseId,
|
||||
'repetitions_unit_id': instance.repetitionsUnitId,
|
||||
'repetitions': instance.repetitions,
|
||||
'weight_unit_id': instance.weightUnitId,
|
||||
'weight': instance.weight,
|
||||
'iteration': instance.iteration,
|
||||
'one_rep_max_estimate': instance.oneRepMaxEstimate,
|
||||
};
|
||||
67
lib/models/trophies/user_trophy_progression.dart
Normal file
67
lib/models/trophies/user_trophy_progression.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/models/trophies/trophy.dart';
|
||||
|
||||
part 'user_trophy_progression.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class UserTrophyProgression {
|
||||
@JsonKey(required: true)
|
||||
final Trophy trophy;
|
||||
|
||||
@JsonKey(required: true, name: 'is_earned')
|
||||
final bool isEarned;
|
||||
|
||||
@JsonKey(required: true, name: 'earned_at', fromJson: utcIso8601ToLocalDateNull)
|
||||
final DateTime? earnedAt;
|
||||
|
||||
/// Progress towards earning the trophy (0-100%)
|
||||
@JsonKey(required: true)
|
||||
final num progress;
|
||||
|
||||
/// Current value towards the trophy goal (e.g., number of workouts completed)
|
||||
@JsonKey(required: true, name: 'current_value', fromJson: stringToNumNull)
|
||||
num? currentValue;
|
||||
|
||||
/// Target value to achieve the trophy goal
|
||||
@JsonKey(required: true, name: 'target_value', fromJson: stringToNumNull)
|
||||
num? targetValue;
|
||||
|
||||
/// Human-readable progress display (e.g., "3 / 10" or "51%")
|
||||
@JsonKey(required: true, name: 'progress_display')
|
||||
String? progressDisplay;
|
||||
|
||||
UserTrophyProgression({
|
||||
required this.trophy,
|
||||
required this.isEarned,
|
||||
required this.earnedAt,
|
||||
required this.progress,
|
||||
required this.currentValue,
|
||||
required this.targetValue,
|
||||
this.progressDisplay,
|
||||
});
|
||||
|
||||
// Boilerplate
|
||||
factory UserTrophyProgression.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserTrophyProgressionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserTrophyProgressionToJson(this);
|
||||
}
|
||||
45
lib/models/trophies/user_trophy_progression.g.dart
Normal file
45
lib/models/trophies/user_trophy_progression.g.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_trophy_progression.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
UserTrophyProgression _$UserTrophyProgressionFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
$checkKeys(
|
||||
json,
|
||||
requiredKeys: const [
|
||||
'trophy',
|
||||
'is_earned',
|
||||
'earned_at',
|
||||
'progress',
|
||||
'current_value',
|
||||
'target_value',
|
||||
'progress_display',
|
||||
],
|
||||
);
|
||||
return UserTrophyProgression(
|
||||
trophy: Trophy.fromJson(json['trophy'] as Map<String, dynamic>),
|
||||
isEarned: json['is_earned'] as bool,
|
||||
earnedAt: utcIso8601ToLocalDateNull(json['earned_at'] as String?),
|
||||
progress: json['progress'] as num,
|
||||
currentValue: stringToNumNull(json['current_value'] as String?),
|
||||
targetValue: stringToNumNull(json['target_value'] as String?),
|
||||
progressDisplay: json['progress_display'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$UserTrophyProgressionToJson(
|
||||
UserTrophyProgression instance,
|
||||
) => <String, dynamic>{
|
||||
'trophy': instance.trophy,
|
||||
'is_earned': instance.isEarned,
|
||||
'earned_at': instance.earnedAt?.toIso8601String(),
|
||||
'progress': instance.progress,
|
||||
'current_value': instance.currentValue,
|
||||
'target_value': instance.targetValue,
|
||||
'progress_display': instance.progressDisplay,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 wger Team
|
||||
* Copyright (c) 2020 - 2026 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
|
||||
@@ -45,7 +45,7 @@ class WgerBaseProvider {
|
||||
this.client = client ?? http.Client();
|
||||
}
|
||||
|
||||
Map<String, String> getDefaultHeaders({bool includeAuth = false}) {
|
||||
Map<String, String> getDefaultHeaders({bool includeAuth = false, String? language}) {
|
||||
final out = {
|
||||
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
|
||||
HttpHeaders.userAgentHeader: auth.getAppNameHeader(),
|
||||
@@ -55,6 +55,10 @@ class WgerBaseProvider {
|
||||
out[HttpHeaders.authorizationHeader] = 'Token ${auth.token}';
|
||||
}
|
||||
|
||||
if (language != null) {
|
||||
out[HttpHeaders.acceptLanguageHeader] = language;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -69,6 +73,7 @@ class WgerBaseProvider {
|
||||
Uri uri, {
|
||||
int maxRetries = 3,
|
||||
Duration initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) async {
|
||||
int attempt = 0;
|
||||
final random = math.Random();
|
||||
@@ -85,7 +90,7 @@ class WgerBaseProvider {
|
||||
while (true) {
|
||||
try {
|
||||
final response = await client
|
||||
.get(uri, headers: getDefaultHeaders(includeAuth: true))
|
||||
.get(uri, headers: getDefaultHeaders(includeAuth: true, language: language))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
@@ -114,13 +119,13 @@ class WgerBaseProvider {
|
||||
}
|
||||
|
||||
/// Fetch and retrieve the overview list of objects, returns the JSON parsed response
|
||||
Future<List<dynamic>> fetchPaginated(Uri uri) async {
|
||||
Future<List<dynamic>> fetchPaginated(Uri uri, {String? language}) async {
|
||||
final out = [];
|
||||
var url = uri;
|
||||
var allPagesProcessed = false;
|
||||
|
||||
while (!allPagesProcessed) {
|
||||
final data = await fetch(url);
|
||||
final data = await fetch(url, language: language);
|
||||
|
||||
data['results'].forEach((e) => out.add(e));
|
||||
|
||||
|
||||
179
lib/providers/trophies.dart
Normal file
179
lib/providers/trophies.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 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:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/trophies/trophy.dart';
|
||||
import 'package:wger/models/trophies/user_trophy.dart';
|
||||
import 'package:wger/models/trophies/user_trophy_progression.dart';
|
||||
import 'package:wger/providers/wger_base_riverpod.dart';
|
||||
|
||||
import 'base_provider.dart';
|
||||
|
||||
part 'trophies.g.dart';
|
||||
|
||||
class TrophyState {
|
||||
final _logger = Logger('TrophyState');
|
||||
|
||||
final List<Trophy> trophies;
|
||||
final List<UserTrophy> userTrophies;
|
||||
final List<UserTrophyProgression> trophyProgression;
|
||||
|
||||
TrophyState({
|
||||
this.trophies = const [],
|
||||
this.userTrophies = const [],
|
||||
this.trophyProgression = const [],
|
||||
});
|
||||
|
||||
TrophyState copyWith({
|
||||
List<Trophy>? trophies,
|
||||
List<UserTrophy>? userTrophies,
|
||||
List<UserTrophyProgression>? trophyProgression,
|
||||
}) {
|
||||
return TrophyState(
|
||||
trophies: trophies ?? this.trophies,
|
||||
userTrophies: userTrophies ?? this.userTrophies,
|
||||
trophyProgression: trophyProgression ?? this.trophyProgression,
|
||||
);
|
||||
}
|
||||
|
||||
List<UserTrophy> get prTrophies =>
|
||||
userTrophies.where((t) => t.trophy.type == TrophyType.pr).toList();
|
||||
|
||||
List<UserTrophy> get nonPrTrophies =>
|
||||
userTrophies.where((t) => t.trophy.type != TrophyType.pr).toList();
|
||||
}
|
||||
|
||||
class TrophyRepository {
|
||||
final _logger = Logger('TrophyRepository');
|
||||
|
||||
final WgerBaseProvider base;
|
||||
final trophiesPath = 'trophy';
|
||||
final userTrophiesPath = 'user-trophy';
|
||||
final userTrophyProgressionPath = 'trophy/progress';
|
||||
|
||||
TrophyRepository(this.base);
|
||||
|
||||
Future<List<Trophy>> fetchTrophies({String? language}) async {
|
||||
try {
|
||||
final url = base.makeUrl(trophiesPath, query: {'limit': API_MAX_PAGE_SIZE});
|
||||
final trophyData = await base.fetchPaginated(url, language: language);
|
||||
return trophyData.map((e) => Trophy.fromJson(e)).toList();
|
||||
} catch (e, stk) {
|
||||
_logger.warning('Error fetching trophies:', e, stk);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<UserTrophy>> fetchUserTrophies({
|
||||
Map<String, String>? filterQuery,
|
||||
String? language,
|
||||
}) async {
|
||||
final query = {'limit': API_MAX_PAGE_SIZE};
|
||||
if (filterQuery != null) {
|
||||
query.addAll(filterQuery);
|
||||
}
|
||||
|
||||
try {
|
||||
final url = base.makeUrl(userTrophiesPath, query: query);
|
||||
final trophyData = await base.fetchPaginated(url, language: language);
|
||||
return trophyData.map((e) => UserTrophy.fromJson(e)).toList();
|
||||
} catch (e, stk) {
|
||||
_logger.warning('Error fetching user trophies:');
|
||||
_logger.warning(e);
|
||||
_logger.warning(stk);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<UserTrophyProgression>> fetchProgression({
|
||||
Map<String, String>? filterQuery,
|
||||
String? language,
|
||||
}) async {
|
||||
try {
|
||||
final url = base.makeUrl(userTrophyProgressionPath, query: filterQuery);
|
||||
final List<dynamic> data = await base.fetch(url, language: language);
|
||||
return data.map((e) => UserTrophyProgression.fromJson(e)).toList();
|
||||
} catch (e, stk) {
|
||||
_logger.warning('Error fetching user trophy progression:', e, stk);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
List<Trophy> filterByType(List<Trophy> list, TrophyType type) =>
|
||||
list.where((t) => t.type == type).toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
TrophyRepository trophyRepository(Ref ref) {
|
||||
final base = ref.read(wgerBaseProvider);
|
||||
return TrophyRepository(base);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
final class TrophyStateNotifier extends _$TrophyStateNotifier {
|
||||
final _logger = Logger('TrophyStateNotifier');
|
||||
|
||||
@override
|
||||
TrophyState build() {
|
||||
return TrophyState();
|
||||
}
|
||||
|
||||
Future<void> fetchAll({String? language}) async {
|
||||
await Future.wait([
|
||||
fetchTrophies(language: language),
|
||||
fetchUserTrophies(language: language),
|
||||
fetchTrophyProgression(language: language),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Fetch all available trophies
|
||||
Future<List<Trophy>> fetchTrophies({String? language}) async {
|
||||
_logger.finer('Fetching trophies');
|
||||
|
||||
final repo = ref.read(trophyRepositoryProvider);
|
||||
final result = await repo.fetchTrophies(language: language);
|
||||
state = state.copyWith(trophies: result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Fetch trophies awarded to the user, excludes hidden trophies
|
||||
Future<List<UserTrophy>> fetchUserTrophies({String? language}) async {
|
||||
_logger.finer('Fetching user trophies');
|
||||
|
||||
final repo = ref.read(trophyRepositoryProvider);
|
||||
final result = await repo.fetchUserTrophies(
|
||||
filterQuery: {'trophy__is_hidden': 'false'}, //'trophy__is_repeatable': 'false'
|
||||
language: language,
|
||||
);
|
||||
state = state.copyWith(userTrophies: result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Fetch trophy progression for the user
|
||||
Future<List<UserTrophyProgression>> fetchTrophyProgression({String? language}) async {
|
||||
_logger.finer('Fetching user trophy progression');
|
||||
|
||||
// Note that repeatable trophies are filtered out in the backend
|
||||
final repo = ref.read(trophyRepositoryProvider);
|
||||
final result = await repo.fetchProgression(language: language);
|
||||
state = state.copyWith(trophyProgression: result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
103
lib/providers/trophies.g.dart
Normal file
103
lib/providers/trophies.g.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'trophies.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(trophyRepository)
|
||||
final trophyRepositoryProvider = TrophyRepositoryProvider._();
|
||||
|
||||
final class TrophyRepositoryProvider
|
||||
extends $FunctionalProvider<TrophyRepository, TrophyRepository, TrophyRepository>
|
||||
with $Provider<TrophyRepository> {
|
||||
TrophyRepositoryProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'trophyRepositoryProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$trophyRepositoryHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<TrophyRepository> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
TrophyRepository create(Ref ref) {
|
||||
return trophyRepository(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(TrophyRepository value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<TrophyRepository>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$trophyRepositoryHash() => r'0699f0c0f7f324f3ba9b21420d9845a3e3096b61';
|
||||
|
||||
@ProviderFor(TrophyStateNotifier)
|
||||
final trophyStateProvider = TrophyStateNotifierProvider._();
|
||||
|
||||
final class TrophyStateNotifierProvider
|
||||
extends $NotifierProvider<TrophyStateNotifier, TrophyState> {
|
||||
TrophyStateNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'trophyStateProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$trophyStateNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TrophyStateNotifier create() => TrophyStateNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(TrophyState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<TrophyState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$trophyStateNotifierHash() => r'c80c732272cf843b698f28152f60b9a5f37ee449';
|
||||
|
||||
abstract class _$TrophyStateNotifier extends $Notifier<TrophyState> {
|
||||
TrophyState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<TrophyState, TrophyState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<TrophyState, TrophyState>,
|
||||
TrophyState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import 'package:wger/models/user/profile.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
|
||||
enum DashboardWidget {
|
||||
trophies('trophies'),
|
||||
routines('routines'),
|
||||
nutrition('nutrition'),
|
||||
weight('weight'),
|
||||
@@ -124,7 +125,13 @@ class UserProvider with ChangeNotifier {
|
||||
// Add any missing widgets (e.g. newly added features)
|
||||
for (final widget in DashboardWidget.values) {
|
||||
if (!loaded.any((item) => item.widget == widget)) {
|
||||
loaded.add(DashboardItem(widget));
|
||||
// Try to insert at the original position defined in the enum
|
||||
// taking into account the current size of the list
|
||||
var index = DashboardWidget.values.indexOf(widget);
|
||||
if (index > loaded.length) {
|
||||
index = loaded.length;
|
||||
}
|
||||
loaded.insert(index, DashboardItem(widget));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
lib/providers/wger_base_riverpod.dart
Normal file
31
lib/providers/wger_base_riverpod.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 - 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:wger/providers/auth.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
|
||||
/// Central provider that maps an existing [AuthProvider] (from the provider package)
|
||||
/// to a [WgerBaseProvider] used by repositories.
|
||||
///
|
||||
/// Usage: ref.watch(wgerBaseProvider(authProvider))
|
||||
final wgerBaseProvider = Provider<WgerBaseProvider>((ref) {
|
||||
throw UnimplementedError(
|
||||
'Override wgerBaseProvider in a ProviderScope with your existing WgerBaseProvider instance',
|
||||
);
|
||||
});
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/helpers/material.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/widgets/core/app_bar.dart';
|
||||
@@ -25,6 +26,7 @@ import 'package:wger/widgets/dashboard/calendar.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/measurements.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/nutrition.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/routines.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/trophies.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/weight.dart';
|
||||
|
||||
class DashboardScreen extends StatelessWidget {
|
||||
@@ -44,22 +46,56 @@ class DashboardScreen extends StatelessWidget {
|
||||
return const DashboardCalendarWidget();
|
||||
case DashboardWidget.nutrition:
|
||||
return const DashboardNutritionWidget();
|
||||
case DashboardWidget.trophies:
|
||||
return const DashboardTrophiesWidget();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: MainAppBar(AppLocalizations.of(context).labelDashboard),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
/*
|
||||
child: Column(
|
||||
children: user.dashboardOrder
|
||||
.where((w) => user.isDashboardWidgetVisible(w))
|
||||
.map(_getDashboardWidget)
|
||||
.toList(),
|
||||
*/
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
final isMobile = width < MATERIAL_XS_BREAKPOINT;
|
||||
final user = Provider.of<UserProvider>(context);
|
||||
|
||||
late final int crossAxisCount;
|
||||
if (width < MATERIAL_XS_BREAKPOINT) {
|
||||
crossAxisCount = 1;
|
||||
} else if (width < MATERIAL_MD_BREAKPOINT) {
|
||||
crossAxisCount = 2;
|
||||
} else if (width < MATERIAL_LG_BREAKPOINT) {
|
||||
crossAxisCount = 3;
|
||||
} else {
|
||||
crossAxisCount = 4;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: MainAppBar(AppLocalizations.of(context).labelDashboard),
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: MATERIAL_LG_BREAKPOINT),
|
||||
child: isMobile
|
||||
? ListView.builder(
|
||||
padding: const EdgeInsets.all(10),
|
||||
itemBuilder: (context, index) => _getDashboardWidget(user.dashboardOrder[index]),
|
||||
itemCount: user.dashboardOrder.length,
|
||||
)
|
||||
: GridView.builder(
|
||||
padding: const EdgeInsets.all(10),
|
||||
itemBuilder: (context, index) =>
|
||||
SingleChildScrollView(child: _getDashboardWidget(user.dashboardOrder[index])),
|
||||
itemCount: user.dashboardOrder.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
childAspectRatio: 0.7,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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) 2026 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,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -30,6 +31,7 @@ import 'package:wger/providers/gallery.dart';
|
||||
import 'package:wger/providers/measurement.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/providers/user.dart';
|
||||
import 'package:wger/screens/dashboard.dart';
|
||||
import 'package:wger/screens/gallery_screen.dart';
|
||||
@@ -37,7 +39,7 @@ import 'package:wger/screens/nutritional_plans_screen.dart';
|
||||
import 'package:wger/screens/routine_list_screen.dart';
|
||||
import 'package:wger/screens/weight_screen.dart';
|
||||
|
||||
class HomeTabsScreen extends StatefulWidget {
|
||||
class HomeTabsScreen extends ConsumerStatefulWidget {
|
||||
final _logger = Logger('HomeTabsScreen');
|
||||
|
||||
HomeTabsScreen();
|
||||
@@ -48,25 +50,20 @@ class HomeTabsScreen extends StatefulWidget {
|
||||
_HomeTabsScreenState createState() => _HomeTabsScreenState();
|
||||
}
|
||||
|
||||
class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProviderStateMixin {
|
||||
late Future<void> _initialData;
|
||||
class _HomeTabsScreenState extends ConsumerState<HomeTabsScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Future<void>? _initialData;
|
||||
bool _errorHandled = false;
|
||||
int _selectedIndex = 0;
|
||||
bool _isWideScreen = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Loading data here, since the build method can be called more than once
|
||||
_initialData = _loadEntries();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
_isWideScreen = size.width > MATERIAL_XS_BREAKPOINT;
|
||||
_initialData ??= _loadEntries();
|
||||
}
|
||||
|
||||
void _onItemTapped(int index) {
|
||||
@@ -85,7 +82,9 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
|
||||
|
||||
/// Load initial data from the server
|
||||
Future<void> _loadEntries() async {
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
final authProvider = context.read<AuthProvider>();
|
||||
final trophyNotifier = ref.read(trophyStateProvider.notifier);
|
||||
|
||||
if (!authProvider.dataInit) {
|
||||
final routinesProvider = context.read<RoutinesProvider>();
|
||||
@@ -127,6 +126,7 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
|
||||
// routinesProvider.fetchAndSetAllRoutinesFull(),
|
||||
weightProvider.fetchAndSetEntries(),
|
||||
measurementProvider.fetchAndSetAllCategoriesAndEntries(),
|
||||
trophyNotifier.fetchAll(language: languageCode),
|
||||
]);
|
||||
|
||||
//
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 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.
|
||||
|
||||
37
lib/screens/trophy_screen.dart
Normal file
37
lib/screens/trophy_screen.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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/core/wide_screen_wrapper.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/widgets/core/app_bar.dart';
|
||||
import 'package:wger/widgets/trophies/trophies_overview.dart';
|
||||
|
||||
class TrophyScreen extends StatelessWidget {
|
||||
const TrophyScreen();
|
||||
|
||||
static const routeName = '/trophies';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: EmptyAppBar(AppLocalizations.of(context).trophies),
|
||||
body: const WidescreenWrapper(child: TrophiesOverview()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class SettingsDashboardVisibility extends StatelessWidget {
|
||||
return i18n.calendar;
|
||||
case DashboardWidget.nutrition:
|
||||
return i18n.nutritionalPlans;
|
||||
case DashboardWidget.trophies:
|
||||
return i18n.trophies;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2026 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.
|
||||
@@ -22,10 +22,10 @@ import 'package:wger/theme/theme.dart';
|
||||
|
||||
class NothingFound extends StatelessWidget {
|
||||
final String _title;
|
||||
final String _titleForm;
|
||||
final Widget _form;
|
||||
final String? _titleForm;
|
||||
final Widget? _form;
|
||||
|
||||
const NothingFound(this._title, this._titleForm, this._form);
|
||||
const NothingFound(this._title, [this._titleForm, this._form]);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -35,21 +35,22 @@ class NothingFound extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(_title),
|
||||
IconButton(
|
||||
iconSize: 30,
|
||||
icon: const Icon(Icons.add_box, color: wgerPrimaryButtonColor),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
_titleForm,
|
||||
hasListView: true,
|
||||
_form,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_titleForm != null && _form != null)
|
||||
IconButton(
|
||||
iconSize: 30,
|
||||
icon: const Icon(Icons.add_box, color: wgerPrimaryButtonColor),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
_titleForm,
|
||||
hasListView: true,
|
||||
_form,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2026 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.
|
||||
@@ -58,7 +58,7 @@ class _DashboardRoutineWidgetState extends State<DashboardRoutineWidget> {
|
||||
),
|
||||
subtitle: Text(
|
||||
_hasContent
|
||||
? '${dateFormat.format(routine!.start)} - ${dateFormat.format(routine!.end)}'
|
||||
? '${dateFormat.format(routine!.start)} - ${dateFormat.format(routine.end)}'
|
||||
: '',
|
||||
),
|
||||
leading: Icon(
|
||||
|
||||
142
lib/widgets/dashboard/widgets/trophies.dart
Normal file
142
lib/widgets/dashboard/widgets/trophies.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020 - 2026 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:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/trophies/trophy.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/screens/trophy_screen.dart';
|
||||
|
||||
class DashboardTrophiesWidget extends ConsumerWidget {
|
||||
const DashboardTrophiesWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final trophiesState = ref.read(trophyStateProvider);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return Card(
|
||||
color: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (trophiesState.nonPrTrophies.isEmpty)
|
||||
Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
i18n.trophies,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
// leading: Icon(Icons.widgets_outlined),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
i18n.noTrophies,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
height: 140,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: trophiesState.nonPrTrophies.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final userTrophy = trophiesState.nonPrTrophies[index];
|
||||
|
||||
return SizedBox(
|
||||
width: 220,
|
||||
child: TrophyCard(trophy: userTrophy.trophy),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TrophyCard extends StatelessWidget {
|
||||
const TrophyCard({
|
||||
super.key,
|
||||
required this.trophy,
|
||||
});
|
||||
|
||||
final Trophy trophy;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card.filled(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(TrophyScreen.routeName);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundImage: NetworkImage(trophy.image),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
trophy.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
trophy.description,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2026 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.
|
||||
@@ -42,7 +42,7 @@ class DashboardWeightWidget extends StatelessWidget {
|
||||
);
|
||||
|
||||
return Consumer<BodyWeightProvider>(
|
||||
builder: (context, _, __) => Card(
|
||||
builder: (context, _, _) => Card(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 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
|
||||
@@ -24,10 +24,12 @@ 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/trophies/user_trophy.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/providers/trophies.dart';
|
||||
import 'package:wger/widgets/core/progress_indicator.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
|
||||
@@ -36,7 +38,6 @@ import '../logs/muscle_groups.dart';
|
||||
|
||||
class WorkoutSummary extends ConsumerStatefulWidget {
|
||||
final _logger = Logger('WorkoutSummary');
|
||||
|
||||
final PageController _controller;
|
||||
|
||||
WorkoutSummary(this._controller);
|
||||
@@ -48,24 +49,39 @@ class WorkoutSummary extends ConsumerStatefulWidget {
|
||||
class _WorkoutSummaryState extends ConsumerState<WorkoutSummary> {
|
||||
late Future<void> _initData;
|
||||
late Routine _routine;
|
||||
bool _didInit = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initData = _reloadRoutineData();
|
||||
}
|
||||
|
||||
Future<void> _reloadRoutineData() async {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (!_didInit) {
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
_initData = _reloadRoutineData(languageCode);
|
||||
_didInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reloadRoutineData(String languageCode) async {
|
||||
widget._logger.fine('Loading routine data');
|
||||
final gymState = ref.read(gymStateProvider);
|
||||
|
||||
_routine = await context.read<RoutinesProvider>().fetchAndSetRoutineFull(
|
||||
gymState.routine.id!,
|
||||
);
|
||||
|
||||
final trophyNotifier = ref.read(trophyStateProvider.notifier);
|
||||
await trophyNotifier.fetchUserTrophies(language: languageCode);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final trophyState = ref.watch(trophyStateProvider);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
@@ -80,12 +96,20 @@ class _WorkoutSummaryState extends ConsumerState<WorkoutSummary> {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const BoxedProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}: ${snapshot.stackTrace}'));
|
||||
widget._logger.warning(snapshot.error);
|
||||
widget._logger.warning(snapshot.stackTrace);
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||
final apiSession = _routine.sessions.firstWhereOrNull(
|
||||
(s) => s.session.date.isSameDayAs(clock.now()),
|
||||
);
|
||||
final userTrophies = trophyState.prTrophies
|
||||
.where((t) => t.contextData?.sessionId == apiSession?.session.id)
|
||||
.toList();
|
||||
|
||||
return WorkoutSessionStats(
|
||||
_routine.sessions.firstWhereOrNull(
|
||||
(s) => s.session.date.isSameDayAs(clock.now()),
|
||||
),
|
||||
apiSession,
|
||||
userTrophies,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,12 +126,14 @@ class _WorkoutSummaryState extends ConsumerState<WorkoutSummary> {
|
||||
class WorkoutSessionStats extends ConsumerWidget {
|
||||
final _logger = Logger('WorkoutSessionStats');
|
||||
final WorkoutSessionApi? _sessionApi;
|
||||
final List<UserTrophy> _userPrTrophies;
|
||||
|
||||
WorkoutSessionStats(this._sessionApi, {super.key});
|
||||
WorkoutSessionStats(this._sessionApi, this._userPrTrophies, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (_sessionApi == null) {
|
||||
return Center(
|
||||
@@ -159,16 +185,19 @@ class WorkoutSessionStats extends ConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 16),
|
||||
// InfoCard(
|
||||
// title: 'Personal Records',
|
||||
// value: prCount.toString(),
|
||||
// color: theme.colorScheme.tertiaryContainer,
|
||||
// ),
|
||||
if (_userPrTrophies.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: InfoCard(
|
||||
title: i18n.personalRecords,
|
||||
value: _userPrTrophies.length.toString(),
|
||||
color: theme.colorScheme.tertiaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
MuscleGroupsCard(_sessionApi.logs),
|
||||
const SizedBox(height: 10),
|
||||
ExercisesCard(_sessionApi),
|
||||
ExercisesCard(_sessionApi, _userPrTrophies),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
ref.read(gymStateProvider.notifier).clear();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 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,33 +17,54 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wger/helpers/date.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
|
||||
import '../gym_mode/summary.dart';
|
||||
import 'exercise_log_chart.dart';
|
||||
import 'muscle_groups.dart';
|
||||
import 'session_info.dart';
|
||||
|
||||
class DayLogWidget extends StatelessWidget {
|
||||
class DayLogWidget extends ConsumerWidget {
|
||||
final DateTime _date;
|
||||
final Routine _routine;
|
||||
|
||||
const DayLogWidget(this._date, this._routine);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final trophyState = ref.read(trophyStateProvider);
|
||||
|
||||
final sessionApi = _routine.sessions.firstWhere(
|
||||
(sessionApi) => sessionApi.session.date.isSameDayAs(_date),
|
||||
);
|
||||
final exercises = sessionApi.exercises;
|
||||
|
||||
final prTrophies = trophyState.prTrophies
|
||||
.where((t) => t.contextData?.sessionId == sessionApi.session.id)
|
||||
.toList();
|
||||
|
||||
return Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Card(child: SessionInfo(sessionApi.session)),
|
||||
if (prTrophies.isNotEmpty)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: InfoCard(
|
||||
title: i18n.personalRecords,
|
||||
value: prTrophies.length.toString(),
|
||||
color: theme.colorScheme.tertiaryContainer,
|
||||
),
|
||||
),
|
||||
MuscleGroupsCard(sessionApi.logs),
|
||||
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
@@ -66,7 +87,17 @@ class DayLogWidget extends StatelessWidget {
|
||||
(log) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(log.repTextNoNl(context)),
|
||||
Row(
|
||||
children: [
|
||||
if (prTrophies.any((t) => t.contextData?.logId == log.id))
|
||||
Icon(
|
||||
Icons.emoji_events,
|
||||
color: theme.colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
Text(log.repTextNoNl(context)),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
key: ValueKey('delete-log-${log.id}'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 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
|
||||
@@ -20,13 +20,15 @@ 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/trophies/user_trophy.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/session_api.dart';
|
||||
|
||||
class ExercisesCard extends StatelessWidget {
|
||||
final WorkoutSessionApi session;
|
||||
final List<UserTrophy> userPrTrophies;
|
||||
|
||||
const ExercisesCard(this.session, {super.key});
|
||||
const ExercisesCard(this.session, this.userPrTrophies, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -44,7 +46,11 @@ class ExercisesCard extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
...exercises.map((exercise) {
|
||||
final logs = session.logs.where((log) => log.exerciseId == exercise.id).toList();
|
||||
return _ExerciseExpansionTile(exercise: exercise, logs: logs);
|
||||
return _ExerciseExpansionTile(
|
||||
exercise: exercise,
|
||||
logs: logs,
|
||||
userPrTrophies: userPrTrophies,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
@@ -57,8 +63,10 @@ class _ExerciseExpansionTile extends StatelessWidget {
|
||||
const _ExerciseExpansionTile({
|
||||
required this.exercise,
|
||||
required this.logs,
|
||||
required this.userPrTrophies,
|
||||
});
|
||||
|
||||
final List<UserTrophy> userPrTrophies;
|
||||
final Exercise exercise;
|
||||
final List<Log> logs;
|
||||
|
||||
@@ -66,6 +74,7 @@ class _ExerciseExpansionTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
final theme = Theme.of(context);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
final topSet = logs.isEmpty
|
||||
? null
|
||||
@@ -77,21 +86,21 @@ class _ExerciseExpansionTile extends StatelessWidget {
|
||||
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(),
|
||||
subtitle: Text(i18n.topSet('$topSetWeight $topSetWeightUnit')),
|
||||
children: logs.map((log) => _SetDataRow(log: log, userPrTrophies: userPrTrophies)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SetDataRow extends StatelessWidget {
|
||||
const _SetDataRow({required this.log});
|
||||
const _SetDataRow({required this.log, required this.userPrTrophies});
|
||||
|
||||
final Log log;
|
||||
final List<UserTrophy> userPrTrophies;
|
||||
|
||||
@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),
|
||||
@@ -103,6 +112,8 @@ class _SetDataRow extends StatelessWidget {
|
||||
log.repTextNoNl(context),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
if (userPrTrophies.any((trophy) => trophy.contextData?.logId == log.id))
|
||||
Icon(Icons.emoji_events, color: theme.colorScheme.primary, size: 20),
|
||||
// if (log.volume() > 0)
|
||||
// Text(
|
||||
// '${log.volume().toStringAsFixed(0)} ${getServerStringTranslation(log.weightUnitObj!.name, context)}',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
* Copyright (c) 2026 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,20 +18,26 @@
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/theme/theme.dart';
|
||||
import 'package:wger/widgets/routines/logs/day_logs_container.dart';
|
||||
|
||||
class WorkoutLogs extends StatelessWidget {
|
||||
class WorkoutLogs extends ConsumerWidget {
|
||||
final Routine _routine;
|
||||
|
||||
const WorkoutLogs(this._routine);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
final trophyNotifier = ref.read(trophyStateProvider.notifier);
|
||||
trophyNotifier.fetchUserTrophies(language: languageCode);
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
children: [
|
||||
|
||||
179
lib/widgets/trophies/trophies_overview.dart
Normal file
179
lib/widgets/trophies/trophies_overview.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 - 2026 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:wger/helpers/material.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/trophies/user_trophy_progression.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
|
||||
class TrophiesOverview extends ConsumerWidget {
|
||||
const TrophiesOverview({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final trophyState = ref.watch(trophyStateProvider);
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
// Responsive grid: determine columns based on screen width
|
||||
final width = MediaQuery.widthOf(context);
|
||||
int crossAxisCount = 1;
|
||||
if (width <= MATERIAL_XS_BREAKPOINT) {
|
||||
crossAxisCount = 2;
|
||||
} else if (width > MATERIAL_XS_BREAKPOINT && width < MATERIAL_MD_BREAKPOINT) {
|
||||
crossAxisCount = 3;
|
||||
} else if (width >= MATERIAL_MD_BREAKPOINT && width < MATERIAL_LG_BREAKPOINT) {
|
||||
crossAxisCount = 4;
|
||||
} else {
|
||||
crossAxisCount = 5;
|
||||
}
|
||||
|
||||
// If empty, show placeholder
|
||||
if (trophyState.trophyProgression.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
i18n.noTrophies,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RepaintBoundary(
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
),
|
||||
key: const ValueKey('trophy-grid'),
|
||||
itemCount: trophyState.trophyProgression.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _TrophyCardImage(userProgression: trophyState.trophyProgression[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrophyCardImage extends StatelessWidget {
|
||||
final UserTrophyProgression userProgression;
|
||||
|
||||
const _TrophyCardImage({required this.userProgression});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
final double progress = (userProgression.progress.toDouble() / 100.0).clamp(0.0, 1.0);
|
||||
|
||||
return Opacity(
|
||||
opacity: userProgression.isEarned ? 1.0 : 0.5,
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.18),
|
||||
width: userProgression.isEarned ? 1.2 : 0,
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 70,
|
||||
height: 70,
|
||||
child: ClipOval(
|
||||
child: Image.network(
|
||||
userProgression.trophy.image,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) => Center(
|
||||
child: Icon(Icons.emoji_events, size: 28, color: colorScheme.primary),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Text(
|
||||
userProgression.trophy.name,
|
||||
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
Text(
|
||||
userProgression.trophy.description,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (userProgression.trophy.isProgressive && !userProgression.isEarned)
|
||||
Tooltip(
|
||||
message: 'Progress: ${userProgression.progressDisplay}',
|
||||
child: SizedBox(
|
||||
height: 6,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
minHeight: 6,
|
||||
valueColor: AlwaysStoppedAnimation(colorScheme.primary),
|
||||
backgroundColor: colorScheme.onSurface.withAlpha((0.06 * 255).round()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (userProgression.isEarned)
|
||||
Positioned(
|
||||
top: 6,
|
||||
right: 6,
|
||||
child: Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.check, size: 16, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,7 @@ import 'package:wger/widgets/core/settings/dashboard_visibility.dart';
|
||||
|
||||
import 'settings_dashboard_visibility_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([
|
||||
UserProvider,
|
||||
WgerBaseProvider,
|
||||
])
|
||||
@GenerateMocks([WgerBaseProvider])
|
||||
void main() {
|
||||
late UserProvider userProvider;
|
||||
late MockWgerBaseProvider mockBaseProvider;
|
||||
@@ -98,24 +95,23 @@ void main() {
|
||||
await tester.pumpWidget(createWidget());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Initial order: routines, nutrition, weight...
|
||||
expect(userProvider.dashboardOrder[0], DashboardWidget.routines);
|
||||
expect(userProvider.dashboardOrder[1], DashboardWidget.nutrition);
|
||||
// Initial order: trophies, routines, nutrition, weight...
|
||||
expect(userProvider.dashboardOrder[0], DashboardWidget.trophies);
|
||||
expect(userProvider.dashboardOrder[1], DashboardWidget.routines);
|
||||
|
||||
// Find drag handle for Routines (index 0)
|
||||
// Find drag handle for Trophies (index 0)
|
||||
final handleFinder = find.byIcon(Icons.drag_handle);
|
||||
final firstHandle = handleFinder.at(0);
|
||||
// final secondHandle = handleFinder.at(1);
|
||||
|
||||
// Drag first item down
|
||||
await tester.drag(firstHandle, const Offset(0, 100)); // Drag down enough to swap
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify order changed
|
||||
// If swapped with second item (nutrition) and maybe third (weight) depending on height
|
||||
// Based on running test: index 0 is nutrition, index 1 is weight.
|
||||
expect(userProvider.dashboardOrder[0], DashboardWidget.nutrition);
|
||||
expect(userProvider.dashboardOrder[1], DashboardWidget.weight);
|
||||
expect(userProvider.dashboardOrder[2], DashboardWidget.routines);
|
||||
// 100px drag seems to skip 2 items (trophies moves to index 2)
|
||||
// [routines, nutrition, trophies, ...]
|
||||
expect(userProvider.dashboardOrder[0], DashboardWidget.routines);
|
||||
expect(userProvider.dashboardOrder[1], DashboardWidget.nutrition);
|
||||
expect(userProvider.dashboardOrder[2], DashboardWidget.trophies);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i9;
|
||||
import 'dart:ui' as _i10;
|
||||
import 'dart:async' as _i5;
|
||||
|
||||
import 'package:flutter/material.dart' as _i7;
|
||||
import 'package:http/http.dart' as _i5;
|
||||
import 'package:http/http.dart' as _i3;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:shared_preferences/shared_preferences.dart' as _i3;
|
||||
import 'package:wger/models/user/profile.dart' as _i8;
|
||||
import 'package:wger/providers/auth.dart' as _i4;
|
||||
import 'package:wger/providers/base_provider.dart' as _i2;
|
||||
import 'package:wger/providers/user.dart' as _i6;
|
||||
import 'package:wger/providers/auth.dart' as _i2;
|
||||
import 'package:wger/providers/base_provider.dart' as _i4;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
@@ -30,234 +25,67 @@ import 'package:wger/providers/user.dart' as _i6;
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider {
|
||||
_FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
class _FakeAuthProvider_0 extends _i1.SmartFake implements _i2.AuthProvider {
|
||||
_FakeAuthProvider_0(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeSharedPreferencesAsync_1 extends _i1.SmartFake implements _i3.SharedPreferencesAsync {
|
||||
_FakeSharedPreferencesAsync_1(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
class _FakeClient_1 extends _i1.SmartFake implements _i3.Client {
|
||||
_FakeClient_1(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeAuthProvider_2 extends _i1.SmartFake implements _i4.AuthProvider {
|
||||
_FakeAuthProvider_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
class _FakeUri_2 extends _i1.SmartFake implements Uri {
|
||||
_FakeUri_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeClient_3 extends _i1.SmartFake implements _i5.Client {
|
||||
_FakeClient_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeUri_4 extends _i1.SmartFake implements Uri {
|
||||
_FakeUri_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeResponse_5 extends _i1.SmartFake implements _i5.Response {
|
||||
_FakeResponse_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [UserProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockUserProvider extends _i1.Mock implements _i6.UserProvider {
|
||||
MockUserProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.ThemeMode get themeMode =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#themeMode),
|
||||
returnValue: _i7.ThemeMode.system,
|
||||
)
|
||||
as _i7.ThemeMode);
|
||||
|
||||
@override
|
||||
_i2.WgerBaseProvider get baseProvider =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#baseProvider),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#baseProvider),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@override
|
||||
_i3.SharedPreferencesAsync get prefs =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#prefs),
|
||||
returnValue: _FakeSharedPreferencesAsync_1(
|
||||
this,
|
||||
Invocation.getter(#prefs),
|
||||
),
|
||||
)
|
||||
as _i3.SharedPreferencesAsync);
|
||||
|
||||
@override
|
||||
List<_i6.DashboardWidget> get dashboardOrder =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#dashboardOrder),
|
||||
returnValue: <_i6.DashboardWidget>[],
|
||||
)
|
||||
as List<_i6.DashboardWidget>);
|
||||
|
||||
@override
|
||||
set themeMode(_i7.ThemeMode? value) => super.noSuchMethod(
|
||||
Invocation.setter(#themeMode, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set prefs(_i3.SharedPreferencesAsync? value) => super.noSuchMethod(
|
||||
Invocation.setter(#prefs, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set profile(_i8.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,
|
||||
);
|
||||
|
||||
@override
|
||||
bool isDashboardWidgetVisible(_i6.DashboardWidget? key) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#isDashboardWidgetVisible, [key]),
|
||||
returnValue: false,
|
||||
)
|
||||
as bool);
|
||||
|
||||
@override
|
||||
_i9.Future<void> setDashboardWidgetVisible(
|
||||
_i6.DashboardWidget? key,
|
||||
bool? visible,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#setDashboardWidgetVisible, [key, visible]),
|
||||
returnValue: _i9.Future<void>.value(),
|
||||
returnValueForMissingStub: _i9.Future<void>.value(),
|
||||
)
|
||||
as _i9.Future<void>);
|
||||
|
||||
@override
|
||||
_i9.Future<void> setDashboardOrder(int? oldIndex, int? newIndex) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#setDashboardOrder, [oldIndex, newIndex]),
|
||||
returnValue: _i9.Future<void>.value(),
|
||||
returnValueForMissingStub: _i9.Future<void>.value(),
|
||||
)
|
||||
as _i9.Future<void>);
|
||||
|
||||
@override
|
||||
void setThemeMode(_i7.ThemeMode? mode) => super.noSuchMethod(
|
||||
Invocation.method(#setThemeMode, [mode]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i9.Future<void> fetchAndSetProfile() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchAndSetProfile, []),
|
||||
returnValue: _i9.Future<void>.value(),
|
||||
returnValueForMissingStub: _i9.Future<void>.value(),
|
||||
)
|
||||
as _i9.Future<void>);
|
||||
|
||||
@override
|
||||
_i9.Future<void> saveProfile() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#saveProfile, []),
|
||||
returnValue: _i9.Future<void>.value(),
|
||||
returnValueForMissingStub: _i9.Future<void>.value(),
|
||||
)
|
||||
as _i9.Future<void>);
|
||||
|
||||
@override
|
||||
_i9.Future<void> verifyEmail() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#verifyEmail, []),
|
||||
returnValue: _i9.Future<void>.value(),
|
||||
returnValueForMissingStub: _i9.Future<void>.value(),
|
||||
)
|
||||
as _i9.Future<void>);
|
||||
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(#addListener, [listener]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(#removeListener, [listener]),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(#dispose, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void notifyListeners() => super.noSuchMethod(
|
||||
Invocation.method(#notifyListeners, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
class _FakeResponse_3 extends _i1.SmartFake implements _i3.Response {
|
||||
_FakeResponse_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [WgerBaseProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
MockWgerBaseProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i4.AuthProvider get auth =>
|
||||
_i2.AuthProvider get auth =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#auth),
|
||||
returnValue: _FakeAuthProvider_2(this, Invocation.getter(#auth)),
|
||||
returnValue: _FakeAuthProvider_0(this, Invocation.getter(#auth)),
|
||||
)
|
||||
as _i4.AuthProvider);
|
||||
as _i2.AuthProvider);
|
||||
|
||||
@override
|
||||
_i5.Client get client =>
|
||||
_i3.Client get client =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#client),
|
||||
returnValue: _FakeClient_3(this, Invocation.getter(#client)),
|
||||
returnValue: _FakeClient_1(this, Invocation.getter(#client)),
|
||||
)
|
||||
as _i5.Client);
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
set auth(_i4.AuthProvider? value) => super.noSuchMethod(
|
||||
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
|
||||
Invocation.setter(#auth, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
set client(_i5.Client? value) => super.noSuchMethod(
|
||||
set client(_i3.Client? value) => super.noSuchMethod(
|
||||
Invocation.setter(#client, value),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -276,7 +104,7 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
[path],
|
||||
{#id: id, #objectMethod: objectMethod, #query: query},
|
||||
),
|
||||
returnValue: _FakeUri_4(
|
||||
returnValue: _FakeUri_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
@@ -288,62 +116,67 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
as Uri);
|
||||
|
||||
@override
|
||||
_i9.Future<dynamic> fetch(
|
||||
_i5.Future<dynamic> fetch(
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i9.Future<dynamic>.value(),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i9.Future<dynamic>);
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i9.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
returnValue: _i9.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i9.Future<List<dynamic>>);
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@override
|
||||
_i9.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#post, [data, uri]),
|
||||
returnValue: _i9.Future<Map<String, dynamic>>.value(
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i9.Future<Map<String, dynamic>>);
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i9.Future<Map<String, dynamic>> patch(
|
||||
_i5.Future<Map<String, dynamic>> patch(
|
||||
Map<String, dynamic>? data,
|
||||
Uri? uri,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#patch, [data, uri]),
|
||||
returnValue: _i9.Future<Map<String, dynamic>>.value(
|
||||
returnValue: _i5.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i9.Future<Map<String, dynamic>>);
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i9.Future<_i5.Response> deleteRequest(String? url, int? id) =>
|
||||
_i5.Future<_i3.Response> deleteRequest(String? url, int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i9.Future<_i5.Response>.value(
|
||||
_FakeResponse_5(
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i9.Future<_i5.Response>);
|
||||
as _i5.Future<_i3.Response>);
|
||||
}
|
||||
|
||||
@@ -1103,10 +1103,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -1141,21 +1145,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i18.Future<dynamic>.value(),
|
||||
)
|
||||
as _i18.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i18.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i18.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i18.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i18.Future<List<dynamic>>);
|
||||
|
||||
@@ -778,6 +778,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get trophies =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#trophies),
|
||||
returnValue: _i3.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#trophies),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get routines =>
|
||||
(super.noSuchMethod(
|
||||
@@ -1107,6 +1118,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get personalRecords =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#personalRecords),
|
||||
returnValue: _i3.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#personalRecords),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get gymMode =>
|
||||
(super.noSuchMethod(
|
||||
@@ -2185,6 +2207,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get noTrophies =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#noTrophies),
|
||||
returnValue: _i3.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#noTrophies),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get noWeightEntries =>
|
||||
(super.noSuchMethod(
|
||||
@@ -3821,6 +3854,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String topSet(String? value) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#topSet, [value]),
|
||||
returnValue: _i3.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#topSet, [value]),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String durationHoursMinutes(int? hours, int? minutes) =>
|
||||
(super.noSuchMethod(
|
||||
|
||||
@@ -368,10 +368,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -406,21 +410,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i14.Future<dynamic>.value(),
|
||||
)
|
||||
as _i14.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i14.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i14.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i14.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i14.Future<List<dynamic>>);
|
||||
|
||||
@@ -141,10 +141,14 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -179,21 +183,26 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<dynamic>.value(),
|
||||
)
|
||||
as _i6.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i6.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i6.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i6.Future<List<dynamic>>);
|
||||
|
||||
@@ -141,10 +141,14 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
as _i6.Future<void>);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -179,21 +183,26 @@ class MockGalleryProvider extends _i1.Mock implements _i4.GalleryProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<dynamic>.value(),
|
||||
)
|
||||
as _i6.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i6.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i6.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i6.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i6.Future<List<dynamic>>);
|
||||
|
||||
@@ -78,10 +78,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -116,21 +120,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@@ -88,10 +88,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -126,21 +130,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@@ -323,10 +323,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i8.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -361,21 +365,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i8.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@@ -167,10 +167,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -205,21 +209,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i20.Future<dynamic>.value(),
|
||||
)
|
||||
as _i20.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i20.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i20.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i20.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i20.Future<List<dynamic>>);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2026 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.
|
||||
@@ -20,6 +20,7 @@ import 'dart:io';
|
||||
|
||||
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';
|
||||
@@ -27,14 +28,16 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/screens/routine_logs_screen.dart';
|
||||
import 'package:wger/screens/routine_screen.dart';
|
||||
import 'package:wger/widgets/routines/logs/log_overview_routine.dart';
|
||||
|
||||
import '../../test_data/routines.dart';
|
||||
import '../test_data/trophies.dart';
|
||||
import 'routine_logs_screen_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([RoutinesProvider])
|
||||
@GenerateMocks([RoutinesProvider, TrophyRepository])
|
||||
void main() {
|
||||
late Routine routine;
|
||||
final mockRoutinesProvider = MockRoutinesProvider();
|
||||
@@ -49,25 +52,39 @@ void main() {
|
||||
Widget renderWidget({locale = 'en'}) {
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
|
||||
return ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: RouteSettings(arguments: routine.id),
|
||||
builder: (_) => const WorkoutLogsScreen(),
|
||||
// Arrange
|
||||
final mockRepository = MockTrophyRepository();
|
||||
when(
|
||||
mockRepository.fetchUserTrophies(
|
||||
filterQuery: anyNamed('filterQuery'),
|
||||
language: anyNamed('language'),
|
||||
),
|
||||
).thenAnswer((_) async => getUserTrophies());
|
||||
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
trophyRepositoryProvider.overrideWithValue(mockRepository),
|
||||
],
|
||||
child: ChangeNotifierProvider<RoutinesProvider>(
|
||||
create: (context) => mockRoutinesProvider,
|
||||
child: MaterialApp(
|
||||
locale: Locale(locale),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
navigatorKey: key,
|
||||
home: TextButton(
|
||||
onPressed: () => key.currentState!.push(
|
||||
MaterialPageRoute<void>(
|
||||
settings: RouteSettings(arguments: routine.id),
|
||||
builder: (_) => const WorkoutLogsScreen(),
|
||||
),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
routes: {
|
||||
RoutineScreen.routeName: (ctx) => const WorkoutLogsScreen(),
|
||||
},
|
||||
),
|
||||
routes: {
|
||||
RoutineScreen.routeName: (ctx) => const WorkoutLogsScreen(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import 'dart:ui' as _i17;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i16;
|
||||
import 'package:wger/models/exercises/exercise.dart' as _i15;
|
||||
import 'package:wger/models/trophies/trophy.dart' as _i19;
|
||||
import 'package:wger/models/trophies/user_trophy.dart' as _i20;
|
||||
import 'package:wger/models/trophies/user_trophy_progression.dart' as _i21;
|
||||
import 'package:wger/models/workouts/base_config.dart' as _i9;
|
||||
import 'package:wger/models/workouts/day.dart' as _i6;
|
||||
import 'package:wger/models/workouts/day_data.dart' as _i14;
|
||||
@@ -21,6 +24,7 @@ import 'package:wger/models/workouts/slot_entry.dart' as _i8;
|
||||
import 'package:wger/models/workouts/weight_unit.dart' as _i3;
|
||||
import 'package:wger/providers/base_provider.dart' as _i2;
|
||||
import 'package:wger/providers/routines.dart' as _i12;
|
||||
import 'package:wger/providers/trophies.dart' as _i18;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
@@ -592,3 +596,107 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider {
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [TrophyRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTrophyRepository extends _i1.Mock implements _i18.TrophyRepository {
|
||||
MockTrophyRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i2.WgerBaseProvider get base =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#base),
|
||||
returnValue: _FakeWgerBaseProvider_0(
|
||||
this,
|
||||
Invocation.getter(#base),
|
||||
),
|
||||
)
|
||||
as _i2.WgerBaseProvider);
|
||||
|
||||
@override
|
||||
String get trophiesPath =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#trophiesPath),
|
||||
returnValue: _i16.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#trophiesPath),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get userTrophiesPath =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#userTrophiesPath),
|
||||
returnValue: _i16.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#userTrophiesPath),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
String get userTrophyProgressionPath =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#userTrophyProgressionPath),
|
||||
returnValue: _i16.dummyValue<String>(
|
||||
this,
|
||||
Invocation.getter(#userTrophyProgressionPath),
|
||||
),
|
||||
)
|
||||
as String);
|
||||
|
||||
@override
|
||||
_i13.Future<List<_i19.Trophy>> fetchTrophies({String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchTrophies, [], {#language: language}),
|
||||
returnValue: _i13.Future<List<_i19.Trophy>>.value(<_i19.Trophy>[]),
|
||||
)
|
||||
as _i13.Future<List<_i19.Trophy>>);
|
||||
|
||||
@override
|
||||
_i13.Future<List<_i20.UserTrophy>> fetchUserTrophies({
|
||||
Map<String, String>? filterQuery,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchUserTrophies, [], {
|
||||
#filterQuery: filterQuery,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: _i13.Future<List<_i20.UserTrophy>>.value(
|
||||
<_i20.UserTrophy>[],
|
||||
),
|
||||
)
|
||||
as _i13.Future<List<_i20.UserTrophy>>);
|
||||
|
||||
@override
|
||||
_i13.Future<List<_i21.UserTrophyProgression>> fetchProgression({
|
||||
Map<String, String>? filterQuery,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchProgression, [], {
|
||||
#filterQuery: filterQuery,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: _i13.Future<List<_i21.UserTrophyProgression>>.value(
|
||||
<_i21.UserTrophyProgression>[],
|
||||
),
|
||||
)
|
||||
as _i13.Future<List<_i21.UserTrophyProgression>>);
|
||||
|
||||
@override
|
||||
List<_i19.Trophy> filterByType(
|
||||
List<_i19.Trophy>? list,
|
||||
_i19.TrophyType? type,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#filterByType, [list, type]),
|
||||
returnValue: <_i19.Trophy>[],
|
||||
)
|
||||
as List<_i19.Trophy>);
|
||||
}
|
||||
|
||||
@@ -78,10 +78,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -116,21 +120,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@@ -117,10 +117,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -155,21 +159,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i11.Future<dynamic>.value(),
|
||||
)
|
||||
as _i11.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i11.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i11.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i11.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i11.Future<List<dynamic>>);
|
||||
|
||||
@@ -78,10 +78,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -116,21 +120,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
85
test/test_data/trophies.dart
Normal file
85
test/test_data/trophies.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:wger/models/trophies/trophy.dart';
|
||||
import 'package:wger/models/trophies/user_trophy.dart';
|
||||
import 'package:wger/models/trophies/user_trophy_progression.dart';
|
||||
|
||||
List<Trophy> getTestTrophies() {
|
||||
return [
|
||||
Trophy(
|
||||
id: 1,
|
||||
uuid: '31a71d9a-bf26-4f18-b82f-afefe6f50df2',
|
||||
name: 'New Year, New Me',
|
||||
description: 'Work out on January 1st',
|
||||
image: 'https://example.com/5362e55b-eaf1-4e34-9ef8-661538a3bdd9.png',
|
||||
type: TrophyType.date,
|
||||
isHidden: false,
|
||||
isProgressive: false,
|
||||
),
|
||||
Trophy(
|
||||
id: 2,
|
||||
uuid: 'b605b6a1-953d-41fb-87c9-a2f88b5f5907',
|
||||
name: 'Unstoppable',
|
||||
description: 'Maintain a 30-day workout streak',
|
||||
image: 'https://example.com/b605b6a1-953d-41fb-87c9-a2f88b5f5907.png',
|
||||
type: TrophyType.sequence,
|
||||
isHidden: false,
|
||||
isProgressive: true,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<UserTrophyProgression> getUserTrophyProgression() {
|
||||
final trophies = getTestTrophies();
|
||||
|
||||
return [
|
||||
UserTrophyProgression(
|
||||
trophy: trophies[0],
|
||||
progress: 100,
|
||||
isEarned: true,
|
||||
earnedAt: DateTime(2025, 12, 20),
|
||||
currentValue: null,
|
||||
targetValue: null,
|
||||
progressDisplay: null,
|
||||
),
|
||||
UserTrophyProgression(
|
||||
trophy: trophies[1],
|
||||
progress: 40,
|
||||
isEarned: false,
|
||||
earnedAt: null,
|
||||
currentValue: 12,
|
||||
targetValue: 30,
|
||||
progressDisplay: '12 / 30',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<UserTrophy> getUserTrophies() {
|
||||
final trophies = getTestTrophies();
|
||||
|
||||
return [
|
||||
UserTrophy(
|
||||
id: 4,
|
||||
earnedAt: DateTime(2025, 12, 20),
|
||||
isNotified: true,
|
||||
progress: 100,
|
||||
trophy: trophies[0],
|
||||
),
|
||||
];
|
||||
}
|
||||
72
test/trophies/models/trophy_test.dart
Normal file
72
test/trophies/models/trophy_test.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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/models/trophies/trophy.dart';
|
||||
|
||||
void main() {
|
||||
group('Trophy model', () {
|
||||
final sampleJson = {
|
||||
'id': 1,
|
||||
'uuid': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'name': 'First Steps',
|
||||
'description': 'Awarded for the first workout',
|
||||
'image': 'https://example.org/trophy.png',
|
||||
'trophy_type': 'count',
|
||||
'is_hidden': false,
|
||||
'is_progressive': true,
|
||||
};
|
||||
|
||||
test('fromJson creates valid Trophy instance', () {
|
||||
final trophy = Trophy.fromJson(sampleJson);
|
||||
|
||||
expect(trophy.id, 1);
|
||||
expect(trophy.uuid, '550e8400-e29b-41d4-a716-446655440000');
|
||||
expect(trophy.name, 'First Steps');
|
||||
expect(trophy.description, 'Awarded for the first workout');
|
||||
expect(trophy.image, 'https://example.org/trophy.png');
|
||||
expect(trophy.type, TrophyType.count);
|
||||
expect(trophy.isHidden, isFalse);
|
||||
expect(trophy.isProgressive, isTrue);
|
||||
});
|
||||
|
||||
test('toJson returns expected map', () {
|
||||
final trophy = Trophy(
|
||||
id: 2,
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
name: 'Progressor',
|
||||
description: 'Progressive trophy',
|
||||
image: 'https://example.org/prog.png',
|
||||
type: TrophyType.time,
|
||||
isHidden: true,
|
||||
isProgressive: false,
|
||||
);
|
||||
|
||||
final json = trophy.toJson();
|
||||
|
||||
expect(json['id'], 2);
|
||||
expect(json['uuid'], '00000000-0000-0000-0000-000000000000');
|
||||
expect(json['name'], 'Progressor');
|
||||
expect(json['description'], 'Progressive trophy');
|
||||
expect(json['image'], 'https://example.org/prog.png');
|
||||
expect(json['trophy_type'], 'time');
|
||||
expect(json['is_hidden'], true);
|
||||
expect(json['is_progressive'], false);
|
||||
});
|
||||
});
|
||||
}
|
||||
97
test/trophies/models/user_trophy_progression_test.dart
Normal file
97
test/trophies/models/user_trophy_progression_test.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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/models/trophies/trophy.dart';
|
||||
import 'package:wger/models/trophies/user_trophy_progression.dart';
|
||||
|
||||
void main() {
|
||||
group('UserTrophyProgression model', () {
|
||||
final trophyJson = {
|
||||
'id': 1,
|
||||
'uuid': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'name': 'First Steps',
|
||||
'description': 'Awarded for the first workout',
|
||||
'image': 'https://example.org/trophy.png',
|
||||
'trophy_type': 'count',
|
||||
'is_hidden': false,
|
||||
'is_progressive': true,
|
||||
};
|
||||
|
||||
final trophyProgressionJson = {
|
||||
'trophy': trophyJson,
|
||||
'is_earned': false,
|
||||
'earned_at': '2020-01-02T15:04:05Z',
|
||||
'progress': 42.5,
|
||||
'current_value': '12.5',
|
||||
'target_value': '100',
|
||||
'progress_display': '12.5/100',
|
||||
};
|
||||
|
||||
test('fromJson creates valid UserTrophyProgression instance', () {
|
||||
final utp = UserTrophyProgression.fromJson(trophyProgressionJson);
|
||||
|
||||
expect(utp.trophy.id, 1);
|
||||
expect(utp.trophy.uuid, '550e8400-e29b-41d4-a716-446655440000');
|
||||
expect(utp.isEarned, isFalse);
|
||||
|
||||
final expectedEarnedAt = DateTime.parse('2020-01-02T15:04:05Z').toLocal();
|
||||
expect(utp.earnedAt, expectedEarnedAt);
|
||||
|
||||
expect(utp.progress, 42.5);
|
||||
expect(utp.currentValue, 12.5);
|
||||
expect(utp.targetValue, 100);
|
||||
expect(utp.progressDisplay, '12.5/100');
|
||||
});
|
||||
|
||||
test('toJson returns expected map', () {
|
||||
final trophy = Trophy(
|
||||
id: 2,
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
name: 'Progressor',
|
||||
description: 'Progressive trophy',
|
||||
image: 'https://example.org/prog.png',
|
||||
type: TrophyType.time,
|
||||
isHidden: true,
|
||||
isProgressive: false,
|
||||
);
|
||||
|
||||
final earnedAt = DateTime.parse('2020-01-02T15:04:05Z').toLocal();
|
||||
|
||||
final utp = UserTrophyProgression(
|
||||
trophy: trophy,
|
||||
isEarned: true,
|
||||
earnedAt: earnedAt,
|
||||
progress: 75,
|
||||
currentValue: 75,
|
||||
targetValue: 100,
|
||||
progressDisplay: '75/100',
|
||||
);
|
||||
|
||||
final json = utp.toJson();
|
||||
|
||||
expect(json['trophy'], same(trophy));
|
||||
expect(json['is_earned'], true);
|
||||
expect(json['earned_at'], earnedAt.toIso8601String());
|
||||
expect(json['progress'], 75);
|
||||
expect(json['current_value'], 75);
|
||||
expect(json['target_value'], 100);
|
||||
expect(json['progress_display'], '75/100');
|
||||
});
|
||||
});
|
||||
}
|
||||
99
test/trophies/provider/trophies_provider_test.dart
Normal file
99
test/trophies/provider/trophies_provider_test.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 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:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:wger/providers/base_provider.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
|
||||
import 'trophies_provider_test.mocks.dart';
|
||||
|
||||
const trophyJson = {
|
||||
'id': 1,
|
||||
'uuid': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'name': 'First Steps',
|
||||
'description': 'Awarded for the first workout',
|
||||
'image': 'https://example.org/trophy.png',
|
||||
'trophy_type': 'count',
|
||||
'is_hidden': false,
|
||||
'is_progressive': true,
|
||||
};
|
||||
|
||||
@GenerateMocks([WgerBaseProvider])
|
||||
void main() {
|
||||
group('Trophy repository', () {
|
||||
test('fetches list of trophies', () async {
|
||||
// Arrange
|
||||
final mockBase = MockWgerBaseProvider();
|
||||
when(mockBase.fetchPaginated(any)).thenAnswer((_) async => [trophyJson]);
|
||||
when(
|
||||
mockBase.makeUrl(
|
||||
any,
|
||||
id: anyNamed('id'),
|
||||
objectMethod: anyNamed('objectMethod'),
|
||||
query: anyNamed('query'),
|
||||
),
|
||||
).thenReturn(Uri.parse('https://example.org/trophies'));
|
||||
final repository = TrophyRepository(mockBase);
|
||||
|
||||
// Act
|
||||
final result = await repository.fetchTrophies();
|
||||
|
||||
// Assert
|
||||
expect(result, isA<List>());
|
||||
expect(result, hasLength(1));
|
||||
final trophy = result.first;
|
||||
expect(trophy.id, 1);
|
||||
expect(trophy.name, 'First Steps');
|
||||
expect(trophy.type.toString(), contains('count'));
|
||||
});
|
||||
|
||||
test('fetches list of user trophy progression', () async {
|
||||
// Arrange
|
||||
final progressionJson = {
|
||||
'trophy': trophyJson,
|
||||
'is_earned': true,
|
||||
'earned_at': '2020-01-02T15:04:05Z',
|
||||
'progress': 42.5,
|
||||
'current_value': '12.5',
|
||||
'target_value': '100',
|
||||
'progress_display': '12.5/100',
|
||||
};
|
||||
|
||||
final mockBase = MockWgerBaseProvider();
|
||||
when(mockBase.fetch(any)).thenAnswer((_) async => [progressionJson]);
|
||||
when(mockBase.makeUrl(any)).thenReturn(Uri.parse('https://example.org/user_progressions'));
|
||||
final repository = TrophyRepository(mockBase);
|
||||
|
||||
// Act
|
||||
final result = await repository.fetchProgression();
|
||||
|
||||
// Assert
|
||||
expect(result, isA<List>());
|
||||
expect(result, hasLength(1));
|
||||
final p = result.first;
|
||||
expect(p.isEarned, isTrue);
|
||||
expect(p.progress, 42.5);
|
||||
expect(p.currentValue, 12.5);
|
||||
expect(p.progressDisplay, '12.5/100');
|
||||
|
||||
verify(mockBase.fetch(any)).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
182
test/trophies/provider/trophies_provider_test.mocks.dart
Normal file
182
test/trophies/provider/trophies_provider_test.mocks.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/trophies/provider/trophies_provider_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i5;
|
||||
|
||||
import 'package:http/http.dart' as _i3;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:wger/providers/auth.dart' as _i2;
|
||||
import 'package:wger/providers/base_provider.dart' as _i4;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// 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);
|
||||
}
|
||||
|
||||
class _FakeClient_1 extends _i1.SmartFake implements _i3.Client {
|
||||
_FakeClient_1(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeUri_2 extends _i1.SmartFake implements Uri {
|
||||
_FakeUri_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
class _FakeResponse_3 extends _i1.SmartFake implements _i3.Response {
|
||||
_FakeResponse_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [WgerBaseProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
MockWgerBaseProvider() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i2.AuthProvider get auth =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#auth),
|
||||
returnValue: _FakeAuthProvider_0(this, Invocation.getter(#auth)),
|
||||
)
|
||||
as _i2.AuthProvider);
|
||||
|
||||
@override
|
||||
_i3.Client get client =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#client),
|
||||
returnValue: _FakeClient_1(this, Invocation.getter(#client)),
|
||||
)
|
||||
as _i3.Client);
|
||||
|
||||
@override
|
||||
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,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
as Map<String, String>);
|
||||
|
||||
@override
|
||||
Uri makeUrl(
|
||||
String? path, {
|
||||
int? id,
|
||||
String? objectMethod,
|
||||
Map<String, dynamic>? query,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
[path],
|
||||
{#id: id, #objectMethod: objectMethod, #query: query},
|
||||
),
|
||||
returnValue: _FakeUri_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#makeUrl,
|
||||
[path],
|
||||
{#id: id, #objectMethod: objectMethod, #query: query},
|
||||
),
|
||||
),
|
||||
)
|
||||
as Uri);
|
||||
|
||||
@override
|
||||
_i5.Future<dynamic> fetch(
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@override
|
||||
_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>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_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>{},
|
||||
),
|
||||
)
|
||||
as _i5.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i5.Future<_i3.Response> deleteRequest(String? url, int? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
returnValue: _i5.Future<_i3.Response>.value(
|
||||
_FakeResponse_3(
|
||||
this,
|
||||
Invocation.method(#deleteRequest, [url, id]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i5.Future<_i3.Response>);
|
||||
}
|
||||
79
test/trophies/widgets/dashboard_trophies_widget_test.dart
Normal file
79
test/trophies/widgets/dashboard_trophies_widget_test.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 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_riverpod/misc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/widgets/dashboard/widgets/trophies.dart';
|
||||
|
||||
import '../../test_data/trophies.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> pumpOverview(WidgetTester tester, [List<Override> overrides = const []]) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: overrides,
|
||||
child: const MaterialApp(
|
||||
locale: Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: DashboardTrophiesWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
group('DashboardTrophiesWidget tests', () {
|
||||
testWidgets('shows trophies', (WidgetTester tester) async {
|
||||
// Act
|
||||
await mockNetworkImagesFor(() async {
|
||||
await pumpOverview(
|
||||
tester,
|
||||
[
|
||||
trophyStateProvider.overrideWithValue(
|
||||
TrophyState(
|
||||
userTrophies: getUserTrophies(),
|
||||
trophies: getTestTrophies(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('New Year, New Me'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('handles empty results', (WidgetTester tester) async {
|
||||
// Act
|
||||
await pumpOverview(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Trophies'), findsOneWidget);
|
||||
expect(find.text('You have no trophies yet'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
62
test/trophies/widgets/trophies_overview_test.dart
Normal file
62
test/trophies/widgets/trophies_overview_test.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 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:network_image_mock/network_image_mock.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/trophies.dart';
|
||||
import 'package:wger/widgets/trophies/trophies_overview.dart';
|
||||
|
||||
import '../../test_data/trophies.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('TrophiesOverview shows trophies', (WidgetTester tester) async {
|
||||
// Act
|
||||
await mockNetworkImagesFor(() async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
trophyStateProvider.overrideWithValue(
|
||||
TrophyState(
|
||||
trophyProgression: getUserTrophyProgression(),
|
||||
userTrophies: getUserTrophies(),
|
||||
trophies: getTestTrophies(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
locale: Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(body: TrophiesOverview()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('New Year, New Me'), findsOneWidget);
|
||||
expect(find.text('Work out on January 1st'), findsOneWidget);
|
||||
|
||||
expect(find.text('Unstoppable'), findsOneWidget);
|
||||
expect(find.text('Maintain a 30-day workout streak'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -106,11 +106,12 @@ void main() {
|
||||
|
||||
group('dashboard config', () {
|
||||
test('initial config should be default (all visible, default order)', () {
|
||||
expect(userProvider.dashboardOrder.length, 5);
|
||||
expect(userProvider.dashboardOrder.length, 6);
|
||||
|
||||
expect(
|
||||
userProvider.dashboardOrder,
|
||||
orderedEquals([
|
||||
DashboardWidget.trophies,
|
||||
DashboardWidget.routines,
|
||||
DashboardWidget.nutrition,
|
||||
DashboardWidget.weight,
|
||||
@@ -161,22 +162,28 @@ void main() {
|
||||
|
||||
// act
|
||||
final newProvider = UserProvider(mockWgerBaseProvider, prefs: prefs);
|
||||
await Future.delayed(const Duration(milliseconds: 50)); // wait for async prefs load
|
||||
await Future.delayed(const Duration(milliseconds: 100)); // wait for async prefs load
|
||||
|
||||
// assert
|
||||
// The loaded ones come first
|
||||
expect(newProvider.dashboardOrder[0], DashboardWidget.nutrition);
|
||||
expect(newProvider.dashboardOrder[1], DashboardWidget.routines);
|
||||
// Loaded: [nutrition, routines]
|
||||
// Missing: trophies (0), weight (3), measurements (4), calendar (5)
|
||||
// 1. trophies (index 0) inserted at 0 -> [trophies, nutrition, routines]
|
||||
// 2. weight (index 3) inserted at 3 -> [trophies, nutrition, routines, weight]
|
||||
|
||||
expect(newProvider.dashboardOrder[0], DashboardWidget.trophies);
|
||||
expect(newProvider.dashboardOrder[1], DashboardWidget.nutrition);
|
||||
expect(newProvider.dashboardOrder[2], DashboardWidget.routines);
|
||||
expect(newProvider.dashboardOrder[3], DashboardWidget.weight);
|
||||
|
||||
// Check visibility
|
||||
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.nutrition), true);
|
||||
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.routines), false);
|
||||
|
||||
// Remaining items are added after
|
||||
expect(newProvider.dashboardOrder.length, 5);
|
||||
|
||||
// Items not in the prefs are visible by default
|
||||
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.weight), true);
|
||||
// Missing items should be visible by default
|
||||
expect(
|
||||
newProvider.isDashboardWidgetVisible(DashboardWidget.weight),
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,10 +78,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -116,21 +120,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
@@ -78,10 +78,14 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
|
||||
Map<String, String> getDefaultHeaders({
|
||||
bool? includeAuth = false,
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getDefaultHeaders, [], {
|
||||
#includeAuth: includeAuth,
|
||||
#language: language,
|
||||
}),
|
||||
returnValue: <String, String>{},
|
||||
)
|
||||
@@ -116,21 +120,26 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
|
||||
Uri? uri, {
|
||||
int? maxRetries = 3,
|
||||
Duration? initialDelay = const Duration(milliseconds: 250),
|
||||
String? language,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#fetch,
|
||||
[uri],
|
||||
{#maxRetries: maxRetries, #initialDelay: initialDelay},
|
||||
{
|
||||
#maxRetries: maxRetries,
|
||||
#initialDelay: initialDelay,
|
||||
#language: language,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
)
|
||||
as _i5.Future<dynamic>);
|
||||
|
||||
@override
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
|
||||
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#fetchPaginated, [uri]),
|
||||
Invocation.method(#fetchPaginated, [uri], {#language: language}),
|
||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
||||
)
|
||||
as _i5.Future<List<dynamic>>);
|
||||
|
||||
Reference in New Issue
Block a user