diff --git a/.github/actions/flutter-common/action.yml b/.github/actions/flutter-common/action.yml
index 800e70ab6..5e52b587b 100644
--- a/.github/actions/flutter-common/action.yml
+++ b/.github/actions/flutter-common/action.yml
@@ -9,7 +9,7 @@ runs:
uses: subosito/flutter-action@v2
with:
channel: stable
- flutter-version: 3.41.9
+ flutter-version: 3.44.0
cache: true
- name: Install Flutter dependencies
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index 435efe115..a1e51813e 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -61,7 +61,7 @@ jobs:
- name: Build application for linux
run: |
sudo apt update
- sudo apt install -y pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev --no-install-recommends
+ sudo apt install -y pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev libsecret-1-dev libjsoncpp-dev --no-install-recommends
flutter build linux --release
tar -zcvf linux-${{ matrix.platform }}.tar.gz build/linux/${{ matrix.platform }}/release/bundle
- uses: actions/upload-artifact@v7
diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml
index d28333fcc..f26dc11ce 100644
--- a/.github/workflows/screenshots.yml
+++ b/.github/workflows/screenshots.yml
@@ -20,12 +20,6 @@ jobs:
- name: Common flutter setup
uses: ./.github/actions/flutter-common
- - name: Install CocoaPods
- run: |
- cd ios
- pod install || true
- cd ..
-
- name: Boot iOS simulator
id: boot
run: |
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 95065c030..6bdaaffc2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -49,6 +49,15 @@
+
+
+
+
+
+
+
+
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 000000000..4f5dac214
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ builders:
+ drift_dev:
+ options:
+ store_date_time_values_as_text: true
\ No newline at end of file
diff --git a/devtools_options.yaml b/devtools_options.yaml
index 75bd2b677..02a606107 100644
--- a/devtools_options.yaml
+++ b/devtools_options.yaml
@@ -1,3 +1,4 @@
extensions:
- drift: true
- - provider: true
\ No newline at end of file
+ - provider: true
+ - shared_preferences: true
\ No newline at end of file
diff --git a/flatpak/de.wger.flutter.desktop b/flatpak/de.wger.flutter.desktop
index 8aaa5559a..57ac3f9f0 100755
--- a/flatpak/de.wger.flutter.desktop
+++ b/flatpak/de.wger.flutter.desktop
@@ -8,7 +8,8 @@ Comment=Fitness/workout, nutrition and weight tracker
# HealthFitness;Exercise;Nutrition
Categories=Education;Utility;Sports;
Icon=de.wger.flutter
-Exec=wger
+Exec=wger %u
+MimeType=x-scheme-handler/wger;
StartupWMClass=wger
X-Purism-FormFactor=Workstation;Mobile;
X-KDE-FormFactors=desktop;tablet;handset;mediacenter;
diff --git a/flatpak/de.wger.flutter.service b/flatpak/de.wger.flutter.service
new file mode 100644
index 000000000..dbf58074e
--- /dev/null
+++ b/flatpak/de.wger.flutter.service
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=de.wger.flutter
+Exec=/app/bin/wger --gapplication-service
diff --git a/integration_test/make_screenshots_test.dart b/integration_test/make_screenshots_test.dart
index 8c13c1df5..80e90f1d5 100644
--- a/integration_test/make_screenshots_test.dart
+++ b/integration_test/make_screenshots_test.dart
@@ -44,8 +44,7 @@ enum DeviceType {
androidWear('wearScreenshots'),
iOSPhoneBig('iPhone 6.9', isAndroid: false),
- iOSPhoneSmall('iPhone 6.7', isAndroid: false)
- ;
+ iOSPhoneSmall('iPhone 6.7', isAndroid: false);
final String folderName;
final bool isAndroid;
diff --git a/integration_test/screenshots_01_dashboard.dart b/integration_test/screenshots_01_dashboard.dart
index 5c9b033b0..5ca4139b1 100644
--- a/integration_test/screenshots_01_dashboard.dart
+++ b/integration_test/screenshots_01_dashboard.dart
@@ -17,131 +17,112 @@
*/
import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mockito/mockito.dart';
-import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/models/workouts/session.dart';
-import 'package:wger/providers/auth.dart';
-import 'package:wger/providers/body_weight.dart';
-import 'package:wger/providers/exercises.dart';
-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/user.dart';
+import 'package:wger/models/measurements/measurement_category.dart';
+import 'package:wger/providers/auth_notifier.dart';
+import 'package:wger/providers/auth_state.dart';
+import 'package:wger/providers/body_weight_repository.dart';
+import 'package:wger/providers/gallery_repository.dart';
+import 'package:wger/providers/ingredient_repository.dart';
+import 'package:wger/providers/measurement_repository.dart';
+import 'package:wger/providers/nutrition_notifier.dart';
+import 'package:wger/providers/nutrition_repository.dart';
+import 'package:wger/providers/routines_notifier.dart';
+import 'package:wger/providers/user_profile_repository.dart';
import 'package:wger/screens/home_tabs_screen.dart';
import 'package:wger/theme/theme.dart';
-import '../test/exercises/contribute_exercise_test.mocks.dart';
import '../test/gallery/gallery_form_test.mocks.dart';
import '../test/measurements/measurement_categories_screen_test.mocks.dart';
import '../test/nutrition/nutritional_plan_screen_test.mocks.dart';
-import '../test/routine/weight_unit_form_widget_test.mocks.dart';
import '../test/weight/weight_screen_test.mocks.dart' as weight;
import '../test_data/body_weight.dart';
import '../test_data/exercises.dart';
+import '../test_data/gallery.dart';
import '../test_data/measurements.dart';
import '../test_data/nutritional_plans.dart';
import '../test_data/profile.dart';
import '../test_data/routines.dart';
-Widget createDashboardScreen({Locale? locale}) {
- locale ??= const Locale('en');
+class _FakeAuthNotifier extends AuthNotifier {
+ _FakeAuthNotifier(this._state);
- final mockGalleryProvider = MockGalleryProvider();
+ final AuthState _state;
- final mockExercisesProvider = MockExercisesProvider();
+ @override
+ Future build() async => _state;
+}
- final mockAuthProvider = MockAuthProvider();
- when(mockAuthProvider.setServerVersion()).thenAnswer((_) async {});
- when(mockAuthProvider.dataInit).thenReturn(true);
+Widget createDashboardScreen({Locale? locale}) {
+ locale ??= const Locale('en');
- final mockWorkoutProvider = MockRoutinesProvider();
- when(mockWorkoutProvider.items).thenReturn([getTestRoutine(exercises: getScreenshotExercises())]);
- when(
- mockWorkoutProvider.currentRoutine,
- ).thenReturn(getTestRoutine(exercises: getScreenshotExercises()));
+ final mockGalleryRepo = MockGalleryRepository();
+ when(mockGalleryRepo.watchAllDrift()).thenAnswer((_) => Stream.value(getTestImages()));
- when(mockWorkoutProvider.fetchSessionData()).thenAnswer(
- (a) => Future.value([
- WorkoutSession(
- routineId: 1,
- date: DateTime.now().add(const Duration(days: -1)),
- timeStart: const TimeOfDay(hour: 17, minute: 34),
- timeEnd: const TimeOfDay(hour: 19, minute: 3),
- impression: 3,
- ),
- ]),
- );
+ final mockNutritionRepo = weight.MockNutritionRepository();
+ final mockIngredientRepo = weight.MockIngredientRepository();
+ when(mockIngredientRepo.getById(any)).thenAnswer((_) async => null);
- final mockNutritionProvider = weight.MockNutritionPlansProvider();
+ final mockBodyWeightRepository = MockBodyWeightRepository();
+ when(
+ mockBodyWeightRepository.watchAllDrift(),
+ ).thenAnswer((_) => Stream.value(getWeightEntries()));
+ final mockMeasurementRepo = MockMeasurementRepository();
when(
- mockNutritionProvider.currentPlan,
- ).thenAnswer((realInvocation) => getNutritionalPlanScreenshot());
- when(mockNutritionProvider.items).thenReturn([getNutritionalPlanScreenshot()]);
+ mockMeasurementRepo.watchAll(),
+ ).thenAnswer((_) => Stream>.value(getMeasurementCategories()));
- final mockWeightProvider = weight.MockBodyWeightProvider();
- when(mockWeightProvider.items).thenReturn(getScreenshotWeightEntries());
+ final mockUserProfileRepo = weight.MockUserProfileRepository();
+ when(
+ mockUserProfileRepo.watchDrift(),
+ ).thenAnswer((_) => Stream.value(tUserProfile1));
- final mockMeasurementProvider = MockMeasurementProvider();
- when(mockMeasurementProvider.categories).thenReturn(getMeasurementCategories());
+ const loggedInAuth = AuthState(
+ status: AuthStatus.loggedIn,
+ token: 'test-token',
+ serverUrl: 'http://localhost',
+ );
+ final container = ProviderContainer.test(
+ overrides: [
+ bodyWeightRepositoryProvider.overrideWithValue(mockBodyWeightRepository),
+ measurementRepositoryProvider.overrideWithValue(mockMeasurementRepo),
+ authProvider.overrideWith(() => _FakeAuthNotifier(loggedInAuth)),
+ userProfileRepositoryProvider.overrideWithValue(mockUserProfileRepo),
+ nutritionRepositoryProvider.overrideWithValue(mockNutritionRepo),
+ ingredientRepositoryProvider.overrideWithValue(mockIngredientRepo),
+ galleryRepositoryProvider.overrideWithValue(mockGalleryRepo),
+ ],
+ );
+ container.read(routinesRiverpodProvider.notifier).state = AsyncData(
+ RoutinesState(
+ routines: [getTestRoutine(exercises: getScreenshotExercises())],
+ ),
+ );
- final mockUserProvider = MockUserProvider();
- when(mockUserProvider.profile).thenReturn(tProfile1);
- when(mockUserProvider.dashboardWidgets).thenReturn([
- DashboardWidget.routines,
- DashboardWidget.weight,
- DashboardWidget.measurements,
- DashboardWidget.calendar,
- DashboardWidget.nutrition,
- DashboardWidget.trophies,
- ]);
+ // Seed the nutrition notifier with the screenshot plan so the dashboard can
+ // show it without going through the server.
+ container.read(nutritionProvider.notifier).state = AsyncData(
+ NutritionState(plans: [getNutritionalPlanScreenshot()]),
+ );
- return riverpod.ProviderScope(
- child: MediaQuery(
- data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first)
- .copyWith(
- padding: EdgeInsets.zero,
- viewPadding: EdgeInsets.zero,
- viewInsets: EdgeInsets.zero,
- ),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockGalleryProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockExercisesProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockAuthProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockUserProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockWorkoutProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockNutritionProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockWeightProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockMeasurementProvider,
- ),
- ],
- child: MaterialApp(
- locale: locale,
- debugShowCheckedModeBanner: false,
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: AppLocalizations.supportedLocales,
- theme: wgerLightTheme,
- home: HomeTabsScreen(),
- ),
+ return MediaQuery(
+ data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
+ padding: EdgeInsets.zero,
+ viewPadding: EdgeInsets.zero,
+ viewInsets: EdgeInsets.zero,
+ ),
+ child: UncontrolledProviderScope(
+ container: container,
+ child: MaterialApp(
+ locale: locale,
+ debugShowCheckedModeBanner: false,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ theme: wgerLightTheme,
+ home: HomeTabsScreen(),
),
),
);
diff --git a/integration_test/screenshots_02_workout.dart b/integration_test/screenshots_02_workout.dart
index 3900dbef9..8cee46765 100644
--- a/integration_test/screenshots_02_workout.dart
+++ b/integration_test/screenshots_02_workout.dart
@@ -17,14 +17,12 @@
*/
import 'package:flutter/material.dart';
-import 'package:mockito/mockito.dart';
-import 'package:provider/provider.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/providers/routines.dart';
+import 'package:wger/providers/routines_notifier.dart';
import 'package:wger/screens/routine_screen.dart';
import 'package:wger/theme/theme.dart';
-import '../test/routine/routine_form_test.mocks.dart';
import '../test_data/exercises.dart';
import '../test_data/routines.dart';
@@ -32,9 +30,13 @@ Widget createWorkoutDetailScreen({Locale? locale}) {
locale ??= const Locale('en');
final key = GlobalKey();
- final mockRoutinesProvider = MockRoutinesProvider();
final routine = getTestRoutine(exercises: getScreenshotExercises());
- when(mockRoutinesProvider.findById(1)).thenReturn(routine);
+ final container = riverpod.ProviderContainer.test();
+ container.read(routinesRiverpodProvider.notifier).state = riverpod.AsyncData(
+ RoutinesState(
+ routines: [getTestRoutine(exercises: getScreenshotExercises())],
+ ),
+ );
return MediaQuery(
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
@@ -42,12 +44,8 @@ Widget createWorkoutDetailScreen({Locale? locale}) {
viewPadding: EdgeInsets.zero,
viewInsets: EdgeInsets.zero,
),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockRoutinesProvider,
- ),
- ],
+ child: riverpod.UncontrolledProviderScope(
+ container: container,
child: MaterialApp(
locale: locale,
debugShowCheckedModeBanner: false,
diff --git a/integration_test/screenshots_03_gym_mode.dart b/integration_test/screenshots_03_gym_mode.dart
index fbc405eed..6f1ba7028 100644
--- a/integration_test/screenshots_03_gym_mode.dart
+++ b/integration_test/screenshots_03_gym_mode.dart
@@ -19,35 +19,45 @@
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
-import 'package:mockito/mockito.dart';
-import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/providers/exercises.dart';
+import 'package:wger/models/workouts/routine.dart';
+import 'package:wger/providers/exercise_filter_state.dart';
+import 'package:wger/providers/exercise_filters_notifier.dart';
import 'package:wger/providers/gym_state.dart';
-import 'package:wger/providers/routines.dart';
-import 'package:wger/providers/wger_base_riverpod.dart';
+import 'package:wger/providers/routines_notifier.dart';
import 'package:wger/screens/gym_mode.dart';
import 'package:wger/screens/routine_screen.dart';
import 'package:wger/theme/theme.dart';
import 'package:wger/widgets/routines/gym_mode/summary.dart';
-import '../test/routine/gym_mode/gym_mode_test.mocks.dart';
import '../test_data/exercises.dart';
import '../test_data/routines.dart';
+class _StubRoutinesRiverpod extends RoutinesRiverpod {
+ _StubRoutinesRiverpod(this._routines);
+ final List _routines;
+
+ @override
+ Stream build() => Stream.value(RoutinesState(routines: _routines));
+}
+
Widget createGymModeScreen({Locale? locale}) {
locale ??= const Locale('en');
final key = GlobalKey();
- final exercises = getTestExercises();
- final routine = getTestRoutine(exercises: getScreenshotExercises());
- final mockRoutinesProvider = MockRoutinesProvider();
- final mockExerciseProvider = MockExercisesProvider();
-
- when(mockRoutinesProvider.fetchAndSetRoutineFull(1)).thenAnswer((_) async => routine);
- when(mockRoutinesProvider.findById(1)).thenAnswer((_) => routine);
- when(mockExerciseProvider.findExerciseById(1)).thenReturn(exercises[0]); // bench press
- when(mockExerciseProvider.findExerciseById(6)).thenReturn(exercises[5]); // side raises
+ final routine = getTestRoutine(exercises: getScreenshotExercises());
+ final container = riverpod.ProviderContainer.test(
+ overrides: [
+ exerciseListFiltersProvider.overrideWithValue(
+ ExerciseFilterState(exercises: getTestExercises()),
+ ),
+ ],
+ );
+ container.read(routinesRiverpodProvider.notifier).state = riverpod.AsyncData(
+ RoutinesState(
+ routines: [getTestRoutine(exercises: getScreenshotExercises())],
+ ),
+ );
return MediaQuery(
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
@@ -55,41 +65,29 @@ Widget createGymModeScreen({Locale? locale}) {
viewPadding: EdgeInsets.zero,
viewInsets: EdgeInsets.zero,
),
- child: riverpod.ProviderScope(
- overrides: [
- wgerBaseProvider.overrideWithValue(MockWgerBaseProvider()),
- ],
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockRoutinesProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockExerciseProvider,
- ),
- ],
- child: MaterialApp(
- locale: locale,
- debugShowCheckedModeBanner: false,
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: AppLocalizations.supportedLocales,
- navigatorKey: key,
- theme: wgerLightTheme,
- home: TextButton(
- onPressed: () => key.currentState!.push(
- MaterialPageRoute(
- settings: RouteSettings(
- arguments: GymModeArguments(routine.id!, routine.days.first.id!, 1),
- ),
- builder: (_) => const GymModeScreen(),
+ child: riverpod.UncontrolledProviderScope(
+ container: container,
+ child: MaterialApp(
+ locale: locale,
+ debugShowCheckedModeBanner: false,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ navigatorKey: key,
+ theme: wgerLightTheme,
+ home: TextButton(
+ onPressed: () => key.currentState!.push(
+ MaterialPageRoute(
+ settings: RouteSettings(
+ arguments: GymModeArguments(routine.id!, routine.days.first.id!, 1),
),
+ builder: (_) => const GymModeScreen(),
),
- child: const SizedBox(),
),
- routes: {
- RoutineScreen.routeName: (ctx) => const RoutineScreen(),
- },
+ child: const SizedBox(),
),
+ routes: {
+ RoutineScreen.routeName: (ctx) => const RoutineScreen(),
+ },
),
),
);
@@ -100,18 +98,11 @@ Widget createGymModeResultsScreen({String locale = 'en'}) {
final key = GlobalKey();
final routine = getTestRoutine(exercises: getScreenshotExercises());
- routine.sessions.first.session.date = clock.now();
-
- final mockRoutinesProvider = MockRoutinesProvider();
- final mockExerciseProvider = MockExercisesProvider();
-
- when(mockRoutinesProvider.fetchAndSetRoutineFull(1)).thenAnswer((_) async => routine);
- when(mockRoutinesProvider.findById(1)).thenAnswer((_) => routine);
+ routine.sessions.first.date = clock.now();
return riverpod.UncontrolledProviderScope(
container: riverpod.ProviderContainer.test(
overrides: [
- wgerBaseProvider.overrideWithValue(MockWgerBaseProvider()),
gymStateProvider.overrideWithValue(
GymModeState(
routine: routine,
@@ -121,31 +112,25 @@ Widget createGymModeResultsScreen({String locale = 'en'}) {
showTimerPages: true,
),
),
- ],
- ),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockRoutinesProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockExerciseProvider,
+ routinesRiverpodProvider.overrideWith(
+ () => _StubRoutinesRiverpod([routine]),
),
],
- child: MaterialApp(
- locale: Locale(locale),
- debugShowCheckedModeBanner: false,
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: AppLocalizations.supportedLocales,
- navigatorKey: key,
- theme: wgerLightTheme,
- home: Scaffold(
- body: PageView(
- controller: controller,
- children: [
- WorkoutSummary(controller),
- ],
- ),
+ ),
+
+ child: MaterialApp(
+ locale: Locale(locale),
+ debugShowCheckedModeBanner: false,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ navigatorKey: key,
+ theme: wgerLightTheme,
+ home: Scaffold(
+ body: PageView(
+ controller: controller,
+ children: [
+ WorkoutSummary(controller),
+ ],
),
),
),
diff --git a/integration_test/screenshots_04_measurements.dart b/integration_test/screenshots_04_measurements.dart
index c42b5af0b..423648f72 100644
--- a/integration_test/screenshots_04_measurements.dart
+++ b/integration_test/screenshots_04_measurements.dart
@@ -17,10 +17,11 @@
*/
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mockito/mockito.dart';
-import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/providers/measurement.dart';
+import 'package:wger/models/measurements/measurement_category.dart';
+import 'package:wger/providers/measurement_repository.dart';
import 'package:wger/screens/measurement_categories_screen.dart';
import 'package:wger/theme/theme.dart';
@@ -30,8 +31,16 @@ import '../test_data/measurements.dart';
Widget createMeasurementScreen({Locale? locale}) {
locale ??= const Locale('en');
- final mockMeasurementProvider = MockMeasurementProvider();
- when(mockMeasurementProvider.categories).thenReturn(getMeasurementCategories());
+ final mockMeasurementRepo = MockMeasurementRepository();
+ when(
+ mockMeasurementRepo.watchAll(),
+ ).thenAnswer((_) => Stream>.value(getMeasurementCategories()));
+
+ final container = ProviderContainer.test(
+ overrides: [
+ measurementRepositoryProvider.overrideWithValue(mockMeasurementRepo),
+ ],
+ );
return MediaQuery(
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
@@ -39,12 +48,8 @@ Widget createMeasurementScreen({Locale? locale}) {
viewPadding: EdgeInsets.zero,
viewInsets: EdgeInsets.zero,
),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockMeasurementProvider,
- ),
- ],
+ child: UncontrolledProviderScope(
+ container: container,
child: MaterialApp(
locale: locale,
debugShowCheckedModeBanner: false,
diff --git a/integration_test/screenshots_05_nutritional_plan.dart b/integration_test/screenshots_05_nutritional_plan.dart
index df5c67285..a0cbd5212 100644
--- a/integration_test/screenshots_05_nutritional_plan.dart
+++ b/integration_test/screenshots_05_nutritional_plan.dart
@@ -16,43 +16,46 @@
* along with this program. If not, see .
*/
-import 'package:drift/native.dart';
import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-import 'package:wger/database/ingredients/ingredients_database.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:mockito/mockito.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/providers/body_weight.dart';
-import 'package:wger/providers/nutrition.dart';
+import 'package:wger/providers/body_weight_repository.dart';
+import 'package:wger/providers/ingredient_repository.dart';
+import 'package:wger/providers/nutrition_repository.dart';
import 'package:wger/screens/nutritional_plan_screen.dart';
import 'package:wger/theme/theme.dart';
-import '../test/user/provider_test.mocks.dart';
+import '../test/nutrition/nutrition_provider_test.mocks.dart';
+import '../test/weight/weight_provider_test.mocks.dart';
+import '../test_data/body_weight.dart';
import '../test_data/nutritional_plans.dart';
Widget createNutritionalPlanScreen({Locale? locale}) {
locale ??= const Locale('en');
- final mockBaseProvider = MockWgerBaseProvider();
final key = GlobalKey();
+ final mockNutritionRepo = MockNutritionRepository();
+ final mockIngredientRepo = MockIngredientRepository();
+ when(mockIngredientRepo.getById(any)).thenAnswer((_) async => null);
+
+ final mockBodyWeightRepository = MockBodyWeightRepository();
+ when(
+ mockBodyWeightRepository.watchAllDrift(),
+ ).thenAnswer((_) => Stream.value(getWeightEntries()));
+
return MediaQuery(
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
padding: EdgeInsets.zero,
viewPadding: EdgeInsets.zero,
viewInsets: EdgeInsets.zero,
),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => NutritionPlansProvider(
- mockBaseProvider,
- [],
- database: IngredientDatabase.inMemory(NativeDatabase.memory()),
- ),
- ),
- ChangeNotifierProvider(
- create: (context) => BodyWeightProvider(mockBaseProvider),
- ),
+ child: ProviderScope(
+ overrides: [
+ bodyWeightRepositoryProvider.overrideWithValue(mockBodyWeightRepository),
+ nutritionRepositoryProvider.overrideWithValue(mockNutritionRepo),
+ ingredientRepositoryProvider.overrideWithValue(mockIngredientRepo),
],
child: MaterialApp(
locale: locale,
diff --git a/integration_test/screenshots_06_weight.dart b/integration_test/screenshots_06_weight.dart
index c58c0e812..384f69e50 100644
--- a/integration_test/screenshots_06_weight.dart
+++ b/integration_test/screenshots_06_weight.dart
@@ -17,17 +17,18 @@
*/
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mockito/mockito.dart';
-import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
-import 'package:wger/providers/body_weight.dart';
-import 'package:wger/providers/nutrition.dart';
-import 'package:wger/providers/user.dart';
+import 'package:wger/providers/body_weight_repository.dart';
+import 'package:wger/providers/ingredient_repository.dart';
+import 'package:wger/providers/nutrition_notifier.dart';
+import 'package:wger/providers/nutrition_repository.dart';
+import 'package:wger/providers/user_profile_repository.dart';
import 'package:wger/screens/form_screen.dart';
import 'package:wger/screens/weight_screen.dart';
import 'package:wger/theme/theme.dart';
-import '../test/utils.dart';
import '../test/weight/weight_screen_test.mocks.dart';
import '../test_data/body_weight.dart';
import '../test_data/nutritional_plans.dart';
@@ -35,15 +36,32 @@ import '../test_data/profile.dart';
Widget createWeightScreen({Locale? locale}) {
locale ??= const Locale('en');
- final weightProvider = BodyWeightProvider(mockBaseProvider);
- weightProvider.items = getScreenshotWeightEntries();
+ final mockBodyWeightRepository = MockBodyWeightRepository();
+ when(
+ mockBodyWeightRepository.watchAllDrift(),
+ ).thenAnswer((_) => Stream.value(getWeightEntries()));
- final mockUserProvider = MockUserProvider();
- when(mockUserProvider.profile).thenReturn(tProfile1);
+ final mockUserProfileRepository = MockUserProfileRepository();
+ when(
+ mockUserProfileRepository.watchDrift(),
+ ).thenAnswer((_) => Stream.value(tUserProfile1));
- final mockNutritionPlansProvider = MockNutritionPlansProvider();
- when(mockNutritionPlansProvider.currentPlan).thenReturn(null);
- when(mockNutritionPlansProvider.items).thenReturn([getNutritionalPlan()]);
+ final mockNutritionRepo = MockNutritionRepository();
+ final mockIngredientRepo = MockIngredientRepository();
+ when(mockIngredientRepo.getById(any)).thenAnswer((_) async => null);
+
+ final container = ProviderContainer(
+ overrides: [
+ bodyWeightRepositoryProvider.overrideWithValue(mockBodyWeightRepository),
+ userProfileRepositoryProvider.overrideWithValue(mockUserProfileRepository),
+ nutritionRepositoryProvider.overrideWithValue(mockNutritionRepo),
+ ingredientRepositoryProvider.overrideWithValue(mockIngredientRepo),
+ ],
+ );
+ // Seed the nutrition notifier with a plan so the weight overview can show it.
+ container.read(nutritionProvider.notifier).state = AsyncData(
+ NutritionState(plans: [getNutritionalPlan()]),
+ );
return MediaQuery(
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
@@ -51,18 +69,8 @@ Widget createWeightScreen({Locale? locale}) {
viewPadding: EdgeInsets.zero,
viewInsets: EdgeInsets.zero,
),
- child: MultiProvider(
- providers: [
- ChangeNotifierProvider(
- create: (context) => mockUserProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => weightProvider,
- ),
- ChangeNotifierProvider(
- create: (context) => mockNutritionPlansProvider,
- ),
- ],
+ child: UncontrolledProviderScope(
+ container: container,
child: MaterialApp(
locale: locale,
debugShowCheckedModeBanner: false,
diff --git a/ios/.gitignore b/ios/.gitignore
index e96ef602b..3b89c9c4f 100644
--- a/ios/.gitignore
+++ b/ios/.gitignore
@@ -10,7 +10,10 @@
**/DerivedData/
Icon?
**/Pods/
+/Podfile
+/Podfile.lock
**/.symlinks/
+**/Flutter/ephemeral/
profile
xcuserdata
**/.generated/
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index e8efba114..592ceee85 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 399e9340e..592ceee85 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
deleted file mode 100644
index 974aea706..000000000
--- a/ios/Podfile
+++ /dev/null
@@ -1,45 +0,0 @@
-# Uncomment this line to define a global platform for your project
-platform :ios, '17.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- target.build_configurations.each do |config|
- config.build_settings['ENABLE_BITCODE'] = 'NO'
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
- end
- end
-end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
deleted file mode 100644
index 21e5c267e..000000000
--- a/ios/Podfile.lock
+++ /dev/null
@@ -1,112 +0,0 @@
-PODS:
- - camera_avfoundation (0.0.1):
- - Flutter
- - Flutter (1.0.0)
- - flutter_native_splash (2.4.3):
- - Flutter
- - flutter_zxing (0.0.1):
- - Flutter
- - image_picker_ios (0.0.1):
- - Flutter
- - integration_test (0.0.1):
- - Flutter
- - package_info_plus (0.4.5):
- - Flutter
- - rive_common (0.0.1):
- - Flutter
- - shared_preferences_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (3.52.0):
- - sqlite3/common (= 3.52.0)
- - sqlite3/common (3.52.0)
- - sqlite3/dbstatvtab (3.52.0):
- - sqlite3/common
- - sqlite3/fts5 (3.52.0):
- - sqlite3/common
- - sqlite3/math (3.52.0):
- - sqlite3/common
- - sqlite3/perf-threadsafe (3.52.0):
- - sqlite3/common
- - sqlite3/rtree (3.52.0):
- - sqlite3/common
- - sqlite3/session (3.52.0):
- - sqlite3/common
- - sqlite3_flutter_libs (0.0.1):
- - Flutter
- - FlutterMacOS
- - sqlite3 (~> 3.52.0)
- - sqlite3/dbstatvtab
- - sqlite3/fts5
- - sqlite3/math
- - sqlite3/perf-threadsafe
- - sqlite3/rtree
- - sqlite3/session
- - url_launcher_ios (0.0.1):
- - Flutter
- - video_player_avfoundation (0.0.1):
- - Flutter
- - FlutterMacOS
-
-DEPENDENCIES:
- - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- - Flutter (from `Flutter`)
- - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- - flutter_zxing (from `.symlinks/plugins/flutter_zxing/ios`)
- - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- - integration_test (from `.symlinks/plugins/integration_test/ios`)
- - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- - rive_common (from `.symlinks/plugins/rive_common/ios`)
- - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
-
-SPEC REPOS:
- trunk:
- - sqlite3
-
-EXTERNAL SOURCES:
- camera_avfoundation:
- :path: ".symlinks/plugins/camera_avfoundation/ios"
- Flutter:
- :path: Flutter
- flutter_native_splash:
- :path: ".symlinks/plugins/flutter_native_splash/ios"
- flutter_zxing:
- :path: ".symlinks/plugins/flutter_zxing/ios"
- image_picker_ios:
- :path: ".symlinks/plugins/image_picker_ios/ios"
- integration_test:
- :path: ".symlinks/plugins/integration_test/ios"
- package_info_plus:
- :path: ".symlinks/plugins/package_info_plus/ios"
- rive_common:
- :path: ".symlinks/plugins/rive_common/ios"
- shared_preferences_foundation:
- :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
- sqlite3_flutter_libs:
- :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
- url_launcher_ios:
- :path: ".symlinks/plugins/url_launcher_ios/ios"
- video_player_avfoundation:
- :path: ".symlinks/plugins/video_player_avfoundation/darwin"
-
-SPEC CHECKSUMS:
- camera_avfoundation: 968a9a5323c79a99c166ad9d7866bfd2047b5a9b
- Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
- flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
- flutter_zxing: fe1338b6d79b8a455d209a28381e336b6cfb4161
- image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
- integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
- package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
- rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
- shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
- sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
- sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
- url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
- video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
-
-PODFILE CHECKSUM: 140d612345f5f5eda8a7ae30f1ccf4e0b07aced5
-
-COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index eb69bbb2b..08bd47e13 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -8,9 +8,9 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 3845DFE0762714C6680D5DFA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFA44D9DB464FB85F130C5B5 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -30,14 +30,13 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 0ED96167FF623FAB319C6E99 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 89F44165E15E0A7B109A05EB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
8D383D452D8E929100066A20 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
@@ -46,8 +45,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- AFA44D9DB464FB85F130C5B5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- C71BAD15819A771165D784B0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -55,24 +52,17 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 3845DFE0762714C6680D5DFA /* Pods_Runner.framework in Frameworks */,
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 23E168F95D2790D29E207E68 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- AFA44D9DB464FB85F130C5B5 /* Pods_Runner.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -88,7 +78,6 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
BB0286322FD60C00014F981C /* Pods */,
- 23E168F95D2790D29E207E68 /* Frameworks */,
);
sourceTree = "";
};
@@ -119,9 +108,6 @@
BB0286322FD60C00014F981C /* Pods */ = {
isa = PBXGroup;
children = (
- C71BAD15819A771165D784B0 /* Pods-Runner.debug.xcconfig */,
- 0ED96167FF623FAB319C6E99 /* Pods-Runner.release.xcconfig */,
- 89F44165E15E0A7B109A05EB /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -133,20 +119,21 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 49A79EC3F389C902853B9186 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 0A9E38C31DF3DC192213822E /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@@ -175,6 +162,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -199,23 +189,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 0A9E38C31DF3DC192213822E /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -232,28 +205,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 49A79EC3F389C902853B9186 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -581,6 +532,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index fc5ae0316..0bd6d4227 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -5,6 +5,24 @@
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 624540307..68c8c578b 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -61,6 +61,19 @@
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ de.wger.flutter.app-auth
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ wger
+
+
+
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
diff --git a/lib/core/error_dialogs.dart b/lib/core/error_dialogs.dart
new file mode 100644
index 000000000..50c1def02
--- /dev/null
+++ b/lib/core/error_dialogs.dart
@@ -0,0 +1,483 @@
+/*
+ * This file is part of wger Workout Manager .
+ * 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 .
+ */
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:logging/logging.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:wger/core/exceptions/http_exception.dart';
+import 'package:wger/helpers/errors.dart';
+import 'package:wger/helpers/logs.dart';
+import 'package:wger/l10n/generated/app_localizations.dart';
+import 'package:wger/main.dart';
+import 'package:wger/models/workouts/log.dart';
+import 'package:wger/providers/workout_logs_notifier.dart';
+
+/// Whether an error dialog is currently on screen.
+///
+/// Errors can fire in quick succession; this guards against stacking several
+/// modal dialogs on top of each other.
+bool _errorDialogVisible = false;
+
+void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? context}) {
+ final logger = Logger('showHttpExceptionErrorDialog');
+
+ // Attempt to get the BuildContext from our global navigatorKey.
+ // This allows us to show a dialog even if the error occurs outside
+ // of a widget's build method.
+ final BuildContext? dialogContext = context ?? navigatorKey.currentContext;
+
+ if (dialogContext == null) {
+ if (kDebugMode) {
+ logger.warning('Error: Could not error show http error dialog because the context is null.');
+ }
+ return;
+ }
+
+ if (_errorDialogVisible) {
+ logger.info('Suppressing error dialog, one is already visible: $exception');
+ return;
+ }
+ _errorDialogVisible = true;
+
+ showDialog(
+ context: dialogContext,
+ builder: (ctx) => AlertDialog(
+ title: Text(AppLocalizations.of(ctx).anErrorOccurred),
+ content: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (exception.type == ErrorType.html)
+ ServerHtmlError(data: exception.htmlError)
+ else
+ ...formatApiErrors(extractErrors(exception.errors)),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ child: Text(MaterialLocalizations.of(ctx).closeButtonLabel),
+ onPressed: () {
+ Navigator.of(ctx).pop();
+ },
+ ),
+ ],
+ ),
+ ).whenComplete(() => _errorDialogVisible = false);
+}
+
+void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext? context}) {
+ // Attempt to get the BuildContext from our global navigatorKey.
+ // This allows us to show a dialog even if the error occurs outside
+ // of a widget's build method.
+ final BuildContext? dialogContext = context ?? navigatorKey.currentContext;
+
+ final logger = Logger('showGeneralErrorDialog');
+
+ if (dialogContext == null) {
+ if (kDebugMode) {
+ logger.warning('Error: Could not error show dialog because the context is null.');
+ }
+ return;
+ }
+
+ if (_errorDialogVisible) {
+ logger.info('Suppressing error dialog, one is already visible: $error');
+ return;
+ }
+ _errorDialogVisible = true;
+
+ final i18n = AppLocalizations.of(dialogContext);
+
+ // If possible, determine the issue title and message based on the error type.
+ // (Note that issue titles and error messages are not localized)
+ String issueTitle = 'An error occurred';
+ String issueErrorMessage = error.toString();
+
+ if (error is FlutterErrorDetails) {
+ issueTitle = 'Application Error';
+ issueErrorMessage = error.exceptionAsString();
+ } else if (error is MissingRequiredKeysException) {
+ issueTitle = 'Missing Required Key';
+ }
+
+ final String fullStackTrace = stackTrace?.toString() ?? 'No stack trace available.';
+ final applicationLogs = InMemoryLogStore().getFormattedLogs();
+
+ showDialog(
+ context: dialogContext,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Row(
+ spacing: 8,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.error, color: Theme.of(context).colorScheme.error),
+ Expanded(
+ child: Text(
+ i18n.anErrorOccurred,
+ style: TextStyle(color: Theme.of(context).colorScheme.error),
+ ),
+ ),
+ ],
+ ),
+ content: SingleChildScrollView(
+ child: ListBody(
+ children: [
+ Text(i18n.errorInfoDescription),
+ const SizedBox(height: 8),
+ Text(i18n.errorInfoDescription2),
+ const SizedBox(height: 10),
+ ExpansionTile(
+ tilePadding: EdgeInsets.zero,
+ title: Text(i18n.errorViewDetails),
+ children: [
+ Text(
+ issueErrorMessage,
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
+ Container(
+ alignment: Alignment.topLeft,
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ constraints: const BoxConstraints(maxHeight: 250),
+ child: SingleChildScrollView(
+ child: Text(
+ fullStackTrace,
+ style: TextStyle(fontSize: 12.0, color: Colors.grey[700]),
+ ),
+ ),
+ ),
+ CopyToClipboardButton(
+ text:
+ 'Error Title: $issueTitle\n'
+ 'Error Message: $issueErrorMessage\n\n'
+ 'Stack Trace:\n$fullStackTrace',
+ ),
+ const SizedBox(height: 8),
+ Text(i18n.applicationLogs, style: const TextStyle(fontWeight: FontWeight.bold)),
+ Container(
+ alignment: Alignment.topLeft,
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ constraints: const BoxConstraints(maxHeight: 250),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ ...applicationLogs.map(
+ (entry) => Text(
+ entry,
+ style: TextStyle(fontSize: 12.0, color: Colors.grey[700]),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ CopyToClipboardButton(text: applicationLogs.join('\n')),
+ ],
+ ),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ child: const Text('Report issue'),
+ onPressed: () async {
+ final githubIssueUrl = buildGithubIssueUrl(
+ issueTitle: issueTitle,
+ issueErrorMessage: issueErrorMessage,
+ stackTrace: fullStackTrace,
+ applicationLogs: applicationLogs,
+ );
+ final Uri reportUri = Uri.parse(githubIssueUrl);
+
+ try {
+ await launchUrl(reportUri, mode: LaunchMode.externalApplication);
+ } catch (e) {
+ if (kDebugMode) {
+ logger.warning('Error launching URL: $e');
+ }
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text('Error opening issue tracker: $e')));
+ }
+ },
+ ),
+ FilledButton(
+ child: Text(MaterialLocalizations.of(context).okButtonLabel),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ );
+ },
+ ).whenComplete(() => _errorDialogVisible = false);
+}
+
+/// Routes [error] to the appropriate UI based on its [ErrorSeverity].
+///
+/// The caller is responsible for logging the error beforehand.
+void handleError(Object? error, StackTrace? stackTrace) {
+ switch (classifyError(error)) {
+ case ErrorSeverity.cosmetic:
+ break;
+ case ErrorSeverity.transient:
+ showTransientErrorSnackbar();
+ case ErrorSeverity.fatal:
+ if (error is WgerHttpException) {
+ showHttpExceptionErrorDialog(error);
+ } else {
+ showGeneralErrorDialog(error, stackTrace);
+ }
+ }
+}
+
+/// Shows a brief, non-blocking snackbar telling the user about a (hopefully)
+/// transient error such as network problems, etc.
+void showTransientErrorSnackbar() {
+ final messenger = scaffoldMessengerKey.currentState;
+ final context = navigatorKey.currentContext;
+
+ if (messenger == null || context == null) {
+ if (kDebugMode) {
+ Logger(
+ 'showNetworkErrorSnackbar',
+ ).warning('Could not show snackbar: no messenger or context available.');
+ }
+ return;
+ }
+
+ messenger
+ ..clearSnackBars()
+ ..showSnackBar(
+ SnackBar(content: Text(AppLocalizations.of(context).errorCouldNotConnectToServer)),
+ );
+}
+
+/// Shows a brief, non-blocking snackbar telling the user their session is no
+/// longer valid and they need to log in again.
+void showSessionExpiredSnackbar() {
+ final messenger = scaffoldMessengerKey.currentState;
+ final context = navigatorKey.currentContext;
+
+ if (messenger == null || context == null) {
+ return;
+ }
+
+ messenger
+ ..clearSnackBars()
+ ..showSnackBar(
+ SnackBar(content: Text(AppLocalizations.of(context).sessionExpired)),
+ );
+}
+
+/// A widget to render HTML errors returned by the server
+///
+/// This is a simple wrapper around the `Html` Widget, with some light changes
+/// to the style.
+class ServerHtmlError extends StatelessWidget {
+ final logger = Logger('ServerHtml');
+ final String data;
+
+ ServerHtmlError({required this.data, super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+
+ return Html(
+ data: data,
+ style: {
+ 'h1': Style(fontSize: FontSize(theme.textTheme.bodyLarge?.fontSize ?? 15)),
+ 'h2': Style(fontSize: FontSize(theme.textTheme.bodyMedium?.fontSize ?? 15)),
+ },
+ doNotRenderTheseTags: const {'a'},
+ );
+ }
+}
+
+class CopyToClipboardButton extends StatelessWidget {
+ final logger = Logger('CopyToClipboardButton');
+ final String text;
+
+ CopyToClipboardButton({required this.text, super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final i18n = AppLocalizations.of(context);
+
+ return TextButton.icon(
+ icon: const Icon(Icons.copy_all_outlined, size: 18),
+ label: Text(i18n.copyToClipboard),
+ style: TextButton.styleFrom(
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ onPressed: () {
+ Clipboard.setData(ClipboardData(text: text))
+ .then((_) {
+ if (context.mounted) {
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(const SnackBar(content: Text('Details copied to clipboard!')));
+ }
+ })
+ .catchError((copyError) {
+ logger.warning('Error copying to clipboard: $copyError');
+
+ if (context.mounted) {
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(const SnackBar(content: Text('Could not copy details.')));
+ }
+ });
+ },
+ );
+ }
+}
+
+void showDeleteLogDialog(BuildContext context, String confirmDeleteName, Log log) async {
+ final res = await showDialog(
+ context: context,
+ builder: (BuildContext contextDialog) {
+ return AlertDialog(
+ content: Text(AppLocalizations.of(context).confirmDelete(confirmDeleteName)),
+ actions: [
+ TextButton(
+ key: const ValueKey('cancel-button'),
+ child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
+ onPressed: () => Navigator.of(contextDialog).pop(),
+ ),
+ TextButton(
+ key: const ValueKey('delete-button'),
+ child: Text(
+ AppLocalizations.of(context).delete,
+ style: TextStyle(color: Theme.of(context).colorScheme.error),
+ ),
+ onPressed: () async {
+ await ProviderScope.containerOf(
+ context,
+ ).read(workoutLogProvider).deleteEntry(log.id.toString());
+
+ Navigator.of(contextDialog).pop();
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ AppLocalizations.of(context).successfullyDeleted,
+ textAlign: TextAlign.center,
+ ),
+ ),
+ );
+ },
+ ),
+ ],
+ );
+ },
+ );
+ return res;
+}
+
+/// Processes the error messages from the server and returns a list of widgets
+List formatApiErrors(List errors, {Color? color}) {
+ final logger = Logger('formatApiErrors');
+ final textColor = color ?? Colors.black;
+
+ final List errorList = [];
+
+ for (final error in errors) {
+ errorList.add(
+ Text(
+ error.key,
+ style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
+ ),
+ );
+
+ logger.warning(error.errorMessages);
+ for (final message in error.errorMessages) {
+ errorList.add(Text(message, style: TextStyle(color: textColor)));
+ }
+ errorList.add(const SizedBox(height: 8));
+ }
+
+ return errorList;
+}
+
+/// Processes the error messages from the server and returns a list of widgets
+List formatTextErrors(List errors, {String? title, Color? color}) {
+ final textColor = color ?? Colors.black;
+
+ final List errorList = [];
+
+ if (title != null) {
+ errorList.add(
+ Text(
+ title,
+ style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
+ ),
+ );
+ }
+
+ for (final message in errors) {
+ errorList.add(Text(message, style: TextStyle(color: textColor)));
+ }
+ errorList.add(const SizedBox(height: 8));
+
+ return errorList;
+}
+
+class FormHttpErrorsWidget extends StatelessWidget {
+ final WgerHttpException exception;
+
+ const FormHttpErrorsWidget(this.exception, {super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+
+ return Container(
+ constraints: const BoxConstraints(maxHeight: 250),
+ decoration: BoxDecoration(
+ border: Border.all(color: theme.colorScheme.error, width: 1),
+ borderRadius: BorderRadius.circular(6),
+ ),
+ padding: const EdgeInsets.all(10),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ Icon(Icons.error_outline, color: theme.colorScheme.error),
+ if (exception.type == ErrorType.html)
+ ServerHtmlError(data: exception.htmlError)
+ else
+ ...formatApiErrors(
+ extractErrors(exception.errors),
+ color: theme.colorScheme.error,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/core/exceptions/mfa_required_exception.dart b/lib/core/exceptions/mfa_required_exception.dart
new file mode 100644
index 000000000..f90f5f9b2
--- /dev/null
+++ b/lib/core/exceptions/mfa_required_exception.dart
@@ -0,0 +1,35 @@
+/*
+ * This file is part of wger Workout Manager .
+ * Copyright (c) 2026 - 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 .
+ */
+
+/// Thrown when an `allauth.headless` login or signup response indicates that
+/// the user must complete a second authentication factor before the session
+/// can be issued tokens.
+///
+/// The [sessionToken] carries the short-lived flow handle that the follow-up
+/// call to `auth/2fa/authenticate` must echo back. [availableFactors] lists
+/// the supported factor ids ('totp', 'recovery_codes', 'webauthn') when the
+/// server included them, so the UI can pick the right input affordance.
+class MfaRequiredException implements Exception {
+ final String sessionToken;
+ final List availableFactors;
+
+ const MfaRequiredException({required this.sessionToken, this.availableFactors = const []});
+
+ @override
+ String toString() => 'MfaRequiredException(factors: $availableFactors, sessionToken: )';
+}
diff --git a/lib/core/locator.dart b/lib/core/locator.dart
deleted file mode 100644
index ff8f2ed29..000000000
--- a/lib/core/locator.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'dart:developer';
-import 'dart:io';
-
-import 'package:drift/native.dart';
-import 'package:get_it/get_it.dart';
-import 'package:wger/database/exercises/exercise_database.dart';
-import 'package:wger/database/ingredients/ingredients_database.dart';
-
-final locator = GetIt.asNewInstance();
-
-class ServiceLocator {
- factory ServiceLocator() => _singleton;
-
- const ServiceLocator._internal();
-
- static const ServiceLocator _singleton = ServiceLocator._internal();
-
- Future _initDB() async {
- ExerciseDatabase exerciseDB;
- IngredientDatabase ingredientDB;
-
- if (Platform.environment.containsKey('FLUTTER_TEST')) {
- exerciseDB = ExerciseDatabase.inMemory(NativeDatabase.memory());
- ingredientDB = IngredientDatabase.inMemory(NativeDatabase.memory());
- } else {
- exerciseDB = ExerciseDatabase();
- ingredientDB = IngredientDatabase();
- }
-
- locator.registerSingleton(exerciseDB);
- locator.registerSingleton(ingredientDB);
- }
-
- Future configure() async {
- try {
- await _initDB();
- } catch (e, _) {
- log(e.toString());
- rethrow;
- }
- }
-}
diff --git a/lib/database/converters/exercise_image_style_converter.dart b/lib/database/converters/exercise_image_style_converter.dart
new file mode 100644
index 000000000..c1e9377ff
--- /dev/null
+++ b/lib/database/converters/exercise_image_style_converter.dart
@@ -0,0 +1,32 @@
+/*
+ * This file is part of wger Workout Manager .
+ * 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 .
+ */
+
+import 'package:drift/drift.dart';
+import 'package:wger/models/exercises/image.dart';
+
+/// Maps an [ExerciseImageStyle] to and from the Django wire format
+/// (`'1'`–`'5'`).
+class ExerciseImageStyleConverter extends TypeConverter {
+ const ExerciseImageStyleConverter();
+
+ @override
+ ExerciseImageStyle fromSql(String fromDb) => ExerciseImageStyle.fromWire(fromDb);
+
+ @override
+ String toSql(ExerciseImageStyle value) => value.wireValue;
+}
diff --git a/lib/database/converters/time_of_day_converter.dart b/lib/database/converters/time_of_day_converter.dart
new file mode 100644
index 000000000..b5ca35bf8
--- /dev/null
+++ b/lib/database/converters/time_of_day_converter.dart
@@ -0,0 +1,35 @@
+/*
+ * This file is part of wger Workout Manager .
+ * Copyright (c) 2020, 2020- wger Team
+ *
+ * wger Workout Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import 'package:drift/drift.dart';
+import 'package:flutter/material.dart';
+import 'package:wger/helpers/json.dart';
+
+class TimeOfDayConverter extends TypeConverter {
+ const TimeOfDayConverter();
+
+ @override
+ TimeOfDay fromSql(String fromDb) {
+ return stringToTimeNull(fromDb)!;
+ }
+
+ @override
+ String toSql(TimeOfDay value) {
+ return timeToString(value)!;
+ }
+}
diff --git a/lib/database/converters/workout_impression_converter.dart b/lib/database/converters/workout_impression_converter.dart
new file mode 100644
index 000000000..c7824be09
--- /dev/null
+++ b/lib/database/converters/workout_impression_converter.dart
@@ -0,0 +1,32 @@
+/*
+ * This file is part of wger Workout Manager .
+ * 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 .
+ */
+
+import 'package:drift/drift.dart';
+import 'package:wger/models/workouts/session.dart';
+
+/// Maps a [WorkoutImpression] to and from the Django wire format
+/// (`'1'`, `'2'`, `'3'`).
+class WorkoutImpressionConverter extends TypeConverter {
+ const WorkoutImpressionConverter();
+
+ @override
+ WorkoutImpression fromSql(String fromDb) => WorkoutImpression.fromWire(fromDb);
+
+ @override
+ String toSql(WorkoutImpression value) => value.wireValue;
+}
diff --git a/lib/database/exercises/exercise_database.dart b/lib/database/exercises/exercise_database.dart
deleted file mode 100644
index 1cbff2113..000000000
--- a/lib/database/exercises/exercise_database.dart
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * This file is part of wger Workout Manager .
- * 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 .
- */
-
-import 'package:drift/drift.dart';
-import 'package:drift_flutter/drift_flutter.dart';
-import 'package:logging/logging.dart';
-import 'package:wger/database/exercises/type_converters.dart';
-import 'package:wger/models/exercises/category.dart';
-import 'package:wger/models/exercises/equipment.dart';
-import 'package:wger/models/exercises/language.dart';
-import 'package:wger/models/exercises/muscle.dart';
-
-part 'exercise_database.g.dart';
-
-@DataClassName('ExerciseTable')
-class Exercises extends Table {
- const Exercises();
-
- IntColumn get id => integer()();
-
- TextColumn get data => text()();
-
- // TextColumn get data => text().map(const ExerciseBaseConverter())();
-
- DateTimeColumn get lastUpdate => dateTime()();
-
- /// The date when the exercise was last fetched from the API. While we know
- /// when the exercise itself was last updated in `lastUpdate`, we can save
- /// ourselves a lot of requests if we don't check too often
- DateTimeColumn get lastFetched => dateTime()();
-}
-
-@DataClassName('MuscleTable')
-class Muscles extends Table {
- const Muscles();
-
- IntColumn get id => integer()();
-
- TextColumn get data => text().map(const MuscleConverter())();
-}
-
-@DataClassName('CategoryTable')
-class Categories extends Table {
- const Categories();
-
- IntColumn get id => integer()();
-
- TextColumn get data => text().map(const ExerciseCategoryConverter())();
-}
-
-@DataClassName('LanguagesTable')
-class Languages extends Table {
- const Languages();
-
- IntColumn get id => integer()();
-
- TextColumn get data => text().map(const LanguageConverter())();
-}
-
-@DataClassName('EquipmentTable')
-class Equipments extends Table {
- const Equipments();
-
- IntColumn get id => integer()();
-
- TextColumn get data => text().map(const EquipmentConverter())();
-}
-
-@DriftDatabase(tables: [Exercises, Muscles, Equipments, Categories, Languages])
-class ExerciseDatabase extends _$ExerciseDatabase {
- final _logger = Logger('ExerciseDatabase');
-
- ExerciseDatabase() : super(_openConnection());
-
- // Named constructor for creating in-memory database
- ExerciseDatabase.inMemory(super.e);
-
- /// Note that this needs to be bumped if the JSON response from the server changes
- @override
- int get schemaVersion => 4;
-
- /// There is not really a migration strategy. If we bump the version
- /// number, delete everything and recreate the new tables. The provider
- /// will fetch everything as needed from the server
- @override
- MigrationStrategy get migration => MigrationStrategy(
- onUpgrade: (m, from, to) async {
- // no-op, but needs to be defined
- return;
- },
- beforeOpen: (openingDetails) async {
- if (openingDetails.hadUpgrade) {
- final m = createMigrator();
- for (final table in allTables) {
- await m.deleteTable(table.actualTableName);
- await m.createTable(table);
- }
- }
- },
- );
-
- Future deleteEverything() {
- return transaction(() async {
- for (final table in allTables) {
- _logger.info('Deleting db cache table ${table.actualTableName}');
- await delete(table).go();
- }
- });
- }
-}
-
-QueryExecutor _openConnection() {
- return driftDatabase(name: 'exercises');
-}
diff --git a/lib/database/exercises/exercise_database.g.dart b/lib/database/exercises/exercise_database.g.dart
deleted file mode 100644
index bfe8ad6e5..000000000
--- a/lib/database/exercises/exercise_database.g.dart
+++ /dev/null
@@ -1,1854 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'exercise_database.dart';
-
-// ignore_for_file: type=lint
-class $ExercisesTable extends Exercises with TableInfo<$ExercisesTable, ExerciseTable> {
- @override
- final GeneratedDatabase attachedDatabase;
- final String? _alias;
- $ExercisesTable(this.attachedDatabase, [this._alias]);
- static const VerificationMeta _idMeta = const VerificationMeta('id');
- @override
- late final GeneratedColumn id = GeneratedColumn(
- 'id',
- aliasedName,
- false,
- type: DriftSqlType.int,
- requiredDuringInsert: true,
- );
- static const VerificationMeta _dataMeta = const VerificationMeta('data');
- @override
- late final GeneratedColumn data = GeneratedColumn(
- 'data',
- aliasedName,
- false,
- type: DriftSqlType.string,
- requiredDuringInsert: true,
- );
- static const VerificationMeta _lastUpdateMeta = const VerificationMeta(
- 'lastUpdate',
- );
- @override
- late final GeneratedColumn lastUpdate = GeneratedColumn(
- 'last_update',
- aliasedName,
- false,
- type: DriftSqlType.dateTime,
- requiredDuringInsert: true,
- );
- static const VerificationMeta _lastFetchedMeta = const VerificationMeta(
- 'lastFetched',
- );
- @override
- late final GeneratedColumn lastFetched = GeneratedColumn(
- 'last_fetched',
- aliasedName,
- false,
- type: DriftSqlType.dateTime,
- requiredDuringInsert: true,
- );
- @override
- List get $columns => [id, data, lastUpdate, lastFetched];
- @override
- String get aliasedName => _alias ?? actualTableName;
- @override
- String get actualTableName => $name;
- static const String $name = 'exercises';
- @override
- VerificationContext validateIntegrity(
- Insertable instance, {
- bool isInserting = false,
- }) {
- final context = VerificationContext();
- final data = instance.toColumns(true);
- if (data.containsKey('id')) {
- context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
- } else if (isInserting) {
- context.missing(_idMeta);
- }
- if (data.containsKey('data')) {
- context.handle(
- _dataMeta,
- this.data.isAcceptableOrUnknown(data['data']!, _dataMeta),
- );
- } else if (isInserting) {
- context.missing(_dataMeta);
- }
- if (data.containsKey('last_update')) {
- context.handle(
- _lastUpdateMeta,
- lastUpdate.isAcceptableOrUnknown(data['last_update']!, _lastUpdateMeta),
- );
- } else if (isInserting) {
- context.missing(_lastUpdateMeta);
- }
- if (data.containsKey('last_fetched')) {
- context.handle(
- _lastFetchedMeta,
- lastFetched.isAcceptableOrUnknown(
- data['last_fetched']!,
- _lastFetchedMeta,
- ),
- );
- } else if (isInserting) {
- context.missing(_lastFetchedMeta);
- }
- return context;
- }
-
- @override
- Set get $primaryKey => const {};
- @override
- ExerciseTable map(Map data, {String? tablePrefix}) {
- final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
- return ExerciseTable(
- id: attachedDatabase.typeMapping.read(
- DriftSqlType.int,
- data['${effectivePrefix}id'],
- )!,
- data: attachedDatabase.typeMapping.read(
- DriftSqlType.string,
- data['${effectivePrefix}data'],
- )!,
- lastUpdate: attachedDatabase.typeMapping.read(
- DriftSqlType.dateTime,
- data['${effectivePrefix}last_update'],
- )!,
- lastFetched: attachedDatabase.typeMapping.read(
- DriftSqlType.dateTime,
- data['${effectivePrefix}last_fetched'],
- )!,
- );
- }
-
- @override
- $ExercisesTable createAlias(String alias) {
- return $ExercisesTable(attachedDatabase, alias);
- }
-}
-
-class ExerciseTable extends DataClass implements Insertable {
- final int id;
- final String data;
- final DateTime lastUpdate;
-
- /// The date when the exercise was last fetched from the API. While we know
- /// when the exercise itself was last updated in `lastUpdate`, we can save
- /// ourselves a lot of requests if we don't check too often
- final DateTime lastFetched;
- const ExerciseTable({
- required this.id,
- required this.data,
- required this.lastUpdate,
- required this.lastFetched,
- });
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- map['id'] = Variable(id);
- map['data'] = Variable(data);
- map['last_update'] = Variable(lastUpdate);
- map['last_fetched'] = Variable(lastFetched);
- return map;
- }
-
- ExercisesCompanion toCompanion(bool nullToAbsent) {
- return ExercisesCompanion(
- id: Value(id),
- data: Value(data),
- lastUpdate: Value(lastUpdate),
- lastFetched: Value(lastFetched),
- );
- }
-
- factory ExerciseTable.fromJson(
- Map json, {
- ValueSerializer? serializer,
- }) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return ExerciseTable(
- id: serializer.fromJson(json['id']),
- data: serializer.fromJson(json['data']),
- lastUpdate: serializer.fromJson(json['lastUpdate']),
- lastFetched: serializer.fromJson(json['lastFetched']),
- );
- }
- @override
- Map toJson({ValueSerializer? serializer}) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return {
- 'id': serializer.toJson(id),
- 'data': serializer.toJson(data),
- 'lastUpdate': serializer.toJson(lastUpdate),
- 'lastFetched': serializer.toJson(lastFetched),
- };
- }
-
- ExerciseTable copyWith({
- int? id,
- String? data,
- DateTime? lastUpdate,
- DateTime? lastFetched,
- }) => ExerciseTable(
- id: id ?? this.id,
- data: data ?? this.data,
- lastUpdate: lastUpdate ?? this.lastUpdate,
- lastFetched: lastFetched ?? this.lastFetched,
- );
- ExerciseTable copyWithCompanion(ExercisesCompanion data) {
- return ExerciseTable(
- id: data.id.present ? data.id.value : this.id,
- data: data.data.present ? data.data.value : this.data,
- lastUpdate: data.lastUpdate.present ? data.lastUpdate.value : this.lastUpdate,
- lastFetched: data.lastFetched.present ? data.lastFetched.value : this.lastFetched,
- );
- }
-
- @override
- String toString() {
- return (StringBuffer('ExerciseTable(')
- ..write('id: $id, ')
- ..write('data: $data, ')
- ..write('lastUpdate: $lastUpdate, ')
- ..write('lastFetched: $lastFetched')
- ..write(')'))
- .toString();
- }
-
- @override
- int get hashCode => Object.hash(id, data, lastUpdate, lastFetched);
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is ExerciseTable &&
- other.id == this.id &&
- other.data == this.data &&
- other.lastUpdate == this.lastUpdate &&
- other.lastFetched == this.lastFetched);
-}
-
-class ExercisesCompanion extends UpdateCompanion {
- final Value id;
- final Value data;
- final Value lastUpdate;
- final Value lastFetched;
- final Value rowid;
- const ExercisesCompanion({
- this.id = const Value.absent(),
- this.data = const Value.absent(),
- this.lastUpdate = const Value.absent(),
- this.lastFetched = const Value.absent(),
- this.rowid = const Value.absent(),
- });
- ExercisesCompanion.insert({
- required int id,
- required String data,
- required DateTime lastUpdate,
- required DateTime lastFetched,
- this.rowid = const Value.absent(),
- }) : id = Value(id),
- data = Value(data),
- lastUpdate = Value(lastUpdate),
- lastFetched = Value(lastFetched);
- static Insertable custom({
- Expression? id,
- Expression? data,
- Expression? lastUpdate,
- Expression? lastFetched,
- Expression? rowid,
- }) {
- return RawValuesInsertable({
- if (id != null) 'id': id,
- if (data != null) 'data': data,
- if (lastUpdate != null) 'last_update': lastUpdate,
- if (lastFetched != null) 'last_fetched': lastFetched,
- if (rowid != null) 'rowid': rowid,
- });
- }
-
- ExercisesCompanion copyWith({
- Value? id,
- Value? data,
- Value? lastUpdate,
- Value? lastFetched,
- Value? rowid,
- }) {
- return ExercisesCompanion(
- id: id ?? this.id,
- data: data ?? this.data,
- lastUpdate: lastUpdate ?? this.lastUpdate,
- lastFetched: lastFetched ?? this.lastFetched,
- rowid: rowid ?? this.rowid,
- );
- }
-
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- if (id.present) {
- map['id'] = Variable(id.value);
- }
- if (data.present) {
- map['data'] = Variable(data.value);
- }
- if (lastUpdate.present) {
- map['last_update'] = Variable(lastUpdate.value);
- }
- if (lastFetched.present) {
- map['last_fetched'] = Variable(lastFetched.value);
- }
- if (rowid.present) {
- map['rowid'] = Variable(rowid.value);
- }
- return map;
- }
-
- @override
- String toString() {
- return (StringBuffer('ExercisesCompanion(')
- ..write('id: $id, ')
- ..write('data: $data, ')
- ..write('lastUpdate: $lastUpdate, ')
- ..write('lastFetched: $lastFetched, ')
- ..write('rowid: $rowid')
- ..write(')'))
- .toString();
- }
-}
-
-class $MusclesTable extends Muscles with TableInfo<$MusclesTable, MuscleTable> {
- @override
- final GeneratedDatabase attachedDatabase;
- final String? _alias;
- $MusclesTable(this.attachedDatabase, [this._alias]);
- static const VerificationMeta _idMeta = const VerificationMeta('id');
- @override
- late final GeneratedColumn id = GeneratedColumn(
- 'id',
- aliasedName,
- false,
- type: DriftSqlType.int,
- requiredDuringInsert: true,
- );
- @override
- late final GeneratedColumnWithTypeConverter data = GeneratedColumn(
- 'data',
- aliasedName,
- false,
- type: DriftSqlType.string,
- requiredDuringInsert: true,
- ).withConverter($MusclesTable.$converterdata);
- @override
- List get $columns => [id, data];
- @override
- String get aliasedName => _alias ?? actualTableName;
- @override
- String get actualTableName => $name;
- static const String $name = 'muscles';
- @override
- VerificationContext validateIntegrity(
- Insertable instance, {
- bool isInserting = false,
- }) {
- final context = VerificationContext();
- final data = instance.toColumns(true);
- if (data.containsKey('id')) {
- context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
- } else if (isInserting) {
- context.missing(_idMeta);
- }
- return context;
- }
-
- @override
- Set get $primaryKey => const {};
- @override
- MuscleTable map(Map data, {String? tablePrefix}) {
- final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
- return MuscleTable(
- id: attachedDatabase.typeMapping.read(
- DriftSqlType.int,
- data['${effectivePrefix}id'],
- )!,
- data: $MusclesTable.$converterdata.fromSql(
- attachedDatabase.typeMapping.read(
- DriftSqlType.string,
- data['${effectivePrefix}data'],
- )!,
- ),
- );
- }
-
- @override
- $MusclesTable createAlias(String alias) {
- return $MusclesTable(attachedDatabase, alias);
- }
-
- static TypeConverter $converterdata = const MuscleConverter();
-}
-
-class MuscleTable extends DataClass implements Insertable {
- final int id;
- final Muscle data;
- const MuscleTable({required this.id, required this.data});
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- map['id'] = Variable(id);
- {
- map['data'] = Variable($MusclesTable.$converterdata.toSql(data));
- }
- return map;
- }
-
- MusclesCompanion toCompanion(bool nullToAbsent) {
- return MusclesCompanion(id: Value(id), data: Value(data));
- }
-
- factory MuscleTable.fromJson(
- Map json, {
- ValueSerializer? serializer,
- }) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return MuscleTable(
- id: serializer.fromJson(json['id']),
- data: serializer.fromJson(json['data']),
- );
- }
- @override
- Map toJson({ValueSerializer? serializer}) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return {
- 'id': serializer.toJson(id),
- 'data': serializer.toJson(data),
- };
- }
-
- MuscleTable copyWith({int? id, Muscle? data}) =>
- MuscleTable(id: id ?? this.id, data: data ?? this.data);
- MuscleTable copyWithCompanion(MusclesCompanion data) {
- return MuscleTable(
- id: data.id.present ? data.id.value : this.id,
- data: data.data.present ? data.data.value : this.data,
- );
- }
-
- @override
- String toString() {
- return (StringBuffer('MuscleTable(')
- ..write('id: $id, ')
- ..write('data: $data')
- ..write(')'))
- .toString();
- }
-
- @override
- int get hashCode => Object.hash(id, data);
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is MuscleTable && other.id == this.id && other.data == this.data);
-}
-
-class MusclesCompanion extends UpdateCompanion {
- final Value id;
- final Value data;
- final Value rowid;
- const MusclesCompanion({
- this.id = const Value.absent(),
- this.data = const Value.absent(),
- this.rowid = const Value.absent(),
- });
- MusclesCompanion.insert({
- required int id,
- required Muscle data,
- this.rowid = const Value.absent(),
- }) : id = Value(id),
- data = Value(data);
- static Insertable custom({
- Expression? id,
- Expression? data,
- Expression? rowid,
- }) {
- return RawValuesInsertable({
- if (id != null) 'id': id,
- if (data != null) 'data': data,
- if (rowid != null) 'rowid': rowid,
- });
- }
-
- MusclesCompanion copyWith({
- Value? id,
- Value? data,
- Value? rowid,
- }) {
- return MusclesCompanion(
- id: id ?? this.id,
- data: data ?? this.data,
- rowid: rowid ?? this.rowid,
- );
- }
-
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- if (id.present) {
- map['id'] = Variable(id.value);
- }
- if (data.present) {
- map['data'] = Variable(
- $MusclesTable.$converterdata.toSql(data.value),
- );
- }
- if (rowid.present) {
- map['rowid'] = Variable(rowid.value);
- }
- return map;
- }
-
- @override
- String toString() {
- return (StringBuffer('MusclesCompanion(')
- ..write('id: $id, ')
- ..write('data: $data, ')
- ..write('rowid: $rowid')
- ..write(')'))
- .toString();
- }
-}
-
-class $EquipmentsTable extends Equipments with TableInfo<$EquipmentsTable, EquipmentTable> {
- @override
- final GeneratedDatabase attachedDatabase;
- final String? _alias;
- $EquipmentsTable(this.attachedDatabase, [this._alias]);
- static const VerificationMeta _idMeta = const VerificationMeta('id');
- @override
- late final GeneratedColumn id = GeneratedColumn(
- 'id',
- aliasedName,
- false,
- type: DriftSqlType.int,
- requiredDuringInsert: true,
- );
- @override
- late final GeneratedColumnWithTypeConverter data = GeneratedColumn(
- 'data',
- aliasedName,
- false,
- type: DriftSqlType.string,
- requiredDuringInsert: true,
- ).withConverter($EquipmentsTable.$converterdata);
- @override
- List get $columns => [id, data];
- @override
- String get aliasedName => _alias ?? actualTableName;
- @override
- String get actualTableName => $name;
- static const String $name = 'equipments';
- @override
- VerificationContext validateIntegrity(
- Insertable instance, {
- bool isInserting = false,
- }) {
- final context = VerificationContext();
- final data = instance.toColumns(true);
- if (data.containsKey('id')) {
- context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
- } else if (isInserting) {
- context.missing(_idMeta);
- }
- return context;
- }
-
- @override
- Set get $primaryKey => const {};
- @override
- EquipmentTable map(Map data, {String? tablePrefix}) {
- final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
- return EquipmentTable(
- id: attachedDatabase.typeMapping.read(
- DriftSqlType.int,
- data['${effectivePrefix}id'],
- )!,
- data: $EquipmentsTable.$converterdata.fromSql(
- attachedDatabase.typeMapping.read(
- DriftSqlType.string,
- data['${effectivePrefix}data'],
- )!,
- ),
- );
- }
-
- @override
- $EquipmentsTable createAlias(String alias) {
- return $EquipmentsTable(attachedDatabase, alias);
- }
-
- static TypeConverter $converterdata = const EquipmentConverter();
-}
-
-class EquipmentTable extends DataClass implements Insertable {
- final int id;
- final Equipment data;
- const EquipmentTable({required this.id, required this.data});
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- map['id'] = Variable(id);
- {
- map['data'] = Variable(
- $EquipmentsTable.$converterdata.toSql(data),
- );
- }
- return map;
- }
-
- EquipmentsCompanion toCompanion(bool nullToAbsent) {
- return EquipmentsCompanion(id: Value(id), data: Value(data));
- }
-
- factory EquipmentTable.fromJson(
- Map json, {
- ValueSerializer? serializer,
- }) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return EquipmentTable(
- id: serializer.fromJson(json['id']),
- data: serializer.fromJson(json['data']),
- );
- }
- @override
- Map toJson({ValueSerializer? serializer}) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return {
- 'id': serializer.toJson(id),
- 'data': serializer.toJson(data),
- };
- }
-
- EquipmentTable copyWith({int? id, Equipment? data}) =>
- EquipmentTable(id: id ?? this.id, data: data ?? this.data);
- EquipmentTable copyWithCompanion(EquipmentsCompanion data) {
- return EquipmentTable(
- id: data.id.present ? data.id.value : this.id,
- data: data.data.present ? data.data.value : this.data,
- );
- }
-
- @override
- String toString() {
- return (StringBuffer('EquipmentTable(')
- ..write('id: $id, ')
- ..write('data: $data')
- ..write(')'))
- .toString();
- }
-
- @override
- int get hashCode => Object.hash(id, data);
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is EquipmentTable && other.id == this.id && other.data == this.data);
-}
-
-class EquipmentsCompanion extends UpdateCompanion {
- final Value id;
- final Value data;
- final Value rowid;
- const EquipmentsCompanion({
- this.id = const Value.absent(),
- this.data = const Value.absent(),
- this.rowid = const Value.absent(),
- });
- EquipmentsCompanion.insert({
- required int id,
- required Equipment data,
- this.rowid = const Value.absent(),
- }) : id = Value(id),
- data = Value(data);
- static Insertable custom({
- Expression? id,
- Expression? data,
- Expression? rowid,
- }) {
- return RawValuesInsertable({
- if (id != null) 'id': id,
- if (data != null) 'data': data,
- if (rowid != null) 'rowid': rowid,
- });
- }
-
- EquipmentsCompanion copyWith({
- Value? id,
- Value? data,
- Value? rowid,
- }) {
- return EquipmentsCompanion(
- id: id ?? this.id,
- data: data ?? this.data,
- rowid: rowid ?? this.rowid,
- );
- }
-
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- if (id.present) {
- map['id'] = Variable(id.value);
- }
- if (data.present) {
- map['data'] = Variable(
- $EquipmentsTable.$converterdata.toSql(data.value),
- );
- }
- if (rowid.present) {
- map['rowid'] = Variable(rowid.value);
- }
- return map;
- }
-
- @override
- String toString() {
- return (StringBuffer('EquipmentsCompanion(')
- ..write('id: $id, ')
- ..write('data: $data, ')
- ..write('rowid: $rowid')
- ..write(')'))
- .toString();
- }
-}
-
-class $CategoriesTable extends Categories with TableInfo<$CategoriesTable, CategoryTable> {
- @override
- final GeneratedDatabase attachedDatabase;
- final String? _alias;
- $CategoriesTable(this.attachedDatabase, [this._alias]);
- static const VerificationMeta _idMeta = const VerificationMeta('id');
- @override
- late final GeneratedColumn id = GeneratedColumn(
- 'id',
- aliasedName,
- false,
- type: DriftSqlType.int,
- requiredDuringInsert: true,
- );
- @override
- late final GeneratedColumnWithTypeConverter data =
- GeneratedColumn(
- 'data',
- aliasedName,
- false,
- type: DriftSqlType.string,
- requiredDuringInsert: true,
- ).withConverter($CategoriesTable.$converterdata);
- @override
- List get $columns => [id, data];
- @override
- String get aliasedName => _alias ?? actualTableName;
- @override
- String get actualTableName => $name;
- static const String $name = 'categories';
- @override
- VerificationContext validateIntegrity(
- Insertable instance, {
- bool isInserting = false,
- }) {
- final context = VerificationContext();
- final data = instance.toColumns(true);
- if (data.containsKey('id')) {
- context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
- } else if (isInserting) {
- context.missing(_idMeta);
- }
- return context;
- }
-
- @override
- Set get $primaryKey => const {};
- @override
- CategoryTable map(Map data, {String? tablePrefix}) {
- final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
- return CategoryTable(
- id: attachedDatabase.typeMapping.read(
- DriftSqlType.int,
- data['${effectivePrefix}id'],
- )!,
- data: $CategoriesTable.$converterdata.fromSql(
- attachedDatabase.typeMapping.read(
- DriftSqlType.string,
- data['${effectivePrefix}data'],
- )!,
- ),
- );
- }
-
- @override
- $CategoriesTable createAlias(String alias) {
- return $CategoriesTable(attachedDatabase, alias);
- }
-
- static TypeConverter $converterdata = const ExerciseCategoryConverter();
-}
-
-class CategoryTable extends DataClass implements Insertable {
- final int id;
- final ExerciseCategory data;
- const CategoryTable({required this.id, required this.data});
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- map['id'] = Variable(id);
- {
- map['data'] = Variable(
- $CategoriesTable.$converterdata.toSql(data),
- );
- }
- return map;
- }
-
- CategoriesCompanion toCompanion(bool nullToAbsent) {
- return CategoriesCompanion(id: Value(id), data: Value(data));
- }
-
- factory CategoryTable.fromJson(
- Map json, {
- ValueSerializer? serializer,
- }) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return CategoryTable(
- id: serializer.fromJson(json['id']),
- data: serializer.fromJson(json['data']),
- );
- }
- @override
- Map toJson({ValueSerializer? serializer}) {
- serializer ??= driftRuntimeOptions.defaultSerializer;
- return {
- 'id': serializer.toJson(id),
- 'data': serializer.toJson(data),
- };
- }
-
- CategoryTable copyWith({int? id, ExerciseCategory? data}) =>
- CategoryTable(id: id ?? this.id, data: data ?? this.data);
- CategoryTable copyWithCompanion(CategoriesCompanion data) {
- return CategoryTable(
- id: data.id.present ? data.id.value : this.id,
- data: data.data.present ? data.data.value : this.data,
- );
- }
-
- @override
- String toString() {
- return (StringBuffer('CategoryTable(')
- ..write('id: $id, ')
- ..write('data: $data')
- ..write(')'))
- .toString();
- }
-
- @override
- int get hashCode => Object.hash(id, data);
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- (other is CategoryTable && other.id == this.id && other.data == this.data);
-}
-
-class CategoriesCompanion extends UpdateCompanion {
- final Value id;
- final Value data;
- final Value rowid;
- const CategoriesCompanion({
- this.id = const Value.absent(),
- this.data = const Value.absent(),
- this.rowid = const Value.absent(),
- });
- CategoriesCompanion.insert({
- required int id,
- required ExerciseCategory data,
- this.rowid = const Value.absent(),
- }) : id = Value(id),
- data = Value(data);
- static Insertable custom({
- Expression? id,
- Expression? data,
- Expression? rowid,
- }) {
- return RawValuesInsertable({
- if (id != null) 'id': id,
- if (data != null) 'data': data,
- if (rowid != null) 'rowid': rowid,
- });
- }
-
- CategoriesCompanion copyWith({
- Value? id,
- Value? data,
- Value? rowid,
- }) {
- return CategoriesCompanion(
- id: id ?? this.id,
- data: data ?? this.data,
- rowid: rowid ?? this.rowid,
- );
- }
-
- @override
- Map toColumns(bool nullToAbsent) {
- final map = {};
- if (id.present) {
- map['id'] = Variable(id.value);
- }
- if (data.present) {
- map['data'] = Variable(
- $CategoriesTable.$converterdata.toSql(data.value),
- );
- }
- if (rowid.present) {
- map['rowid'] = Variable(rowid.value);
- }
- return map;
- }
-
- @override
- String toString() {
- return (StringBuffer('CategoriesCompanion(')
- ..write('id: $id, ')
- ..write('data: $data, ')
- ..write('rowid: $rowid')
- ..write(')'))
- .toString();
- }
-}
-
-class $LanguagesTable extends Languages with TableInfo<$LanguagesTable, LanguagesTable> {
- @override
- final GeneratedDatabase attachedDatabase;
- final String? _alias;
- $LanguagesTable(this.attachedDatabase, [this._alias]);
- static const VerificationMeta _idMeta = const VerificationMeta('id');
- @override
- late final GeneratedColumn id = GeneratedColumn(
- 'id',
- aliasedName,
- false,
- type: DriftSqlType.int,
- requiredDuringInsert: true,
- );
- @override
- late final GeneratedColumnWithTypeConverter data = GeneratedColumn(
- 'data',
- aliasedName,
- false,
- type: DriftSqlType.string,
- requiredDuringInsert: true,
- ).withConverter($LanguagesTable.$converterdata);
- @override
- List get $columns => [id, data];
- @override
- String get aliasedName => _alias ?? actualTableName;
- @override
- String get actualTableName => $name;
- static const String $name = 'languages';
- @override
- VerificationContext validateIntegrity(
- Insertable instance, {
- bool isInserting = false,
- }) {
- final context = VerificationContext();
- final data = instance.toColumns(true);
- if (data.containsKey('id')) {
- context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
- } else if (isInserting) {
- context.missing(_idMeta);
- }
- return context;
- }
-
- @override
- Set get $primaryKey => const {};
- @override
- LanguagesTable map(Map data, {String? tablePrefix}) {
- final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
- return LanguagesTable(
- id: attachedDatabase.typeMapping.read(
- DriftSqlType.int,
- data['${effectivePrefix}id'],
- )!,
- data: $LanguagesTable.$converterdata.fromSql(
- attachedDatabase.typeMapping.read(
- DriftSqlType.string,
- data['${effectivePrefix}data'],
- )!,
- ),
- );
- }
-
- @override
- $LanguagesTable createAlias(String alias) {
- return $LanguagesTable(attachedDatabase, alias);
- }
-
- static TypeConverter $converterdata = const LanguageConverter();
-}
-
-class LanguagesTable extends DataClass implements Insertable {
- final int id;
- final Language data;
- const LanguagesTable({required this.id, required this.data});
- @override
- Map toColumns(bool nullToAbsent) {
- final map =