Move more usages to new exercise provider

This commit is contained in:
Roland Geider
2025-10-25 18:10:50 +02:00
parent ca007d2335
commit 3dc9519445
27 changed files with 299 additions and 1383 deletions

View File

@@ -1,3 +1,4 @@
extensions:
- drift: true
- provider: true
- provider: true
- shared_preferences: true

View File

@@ -1,6 +1,8 @@
PODS:
- camera_avfoundation (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_keyboard_visibility (0.0.1):
- Flutter
@@ -60,6 +62,7 @@ PODS:
DEPENDENCIES:
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- Flutter (from `Flutter`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_zxing (from `.symlinks/plugins/flutter_zxing/ios`)
@@ -83,6 +86,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
Flutter:
:path: Flutter
flutter_keyboard_visibility:
@@ -114,6 +119,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93

View File

@@ -12,13 +12,11 @@ import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/models/exercises/translation.dart';
import 'package:wger/models/exercises/video.dart';
import 'package:wger/models/measurements/measurement_category.dart';
import 'package:wger/models/workouts/log.dart';
import 'powersync.dart';
import 'tables/exercise.dart';
import 'tables/language.dart';
import 'tables/measurements.dart';
import 'tables/routines.dart';
import 'tables/weight.dart';
part 'database.g.dart';
@@ -43,7 +41,7 @@ part 'database.g.dart';
// User data
WeightEntryTable,
MeasurementCategoryTable,
WorkoutLogTable,
// WorkoutLogTable,
],
//include: {'queries.drift'},
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
import 'package:drift/drift.dart';
import 'package:powersync/powersync.dart' show uuid;
import 'package:wger/models/workouts/log.dart';
@UseRowClass(Log)
// @UseRowClass(Log)
class WorkoutLogTable extends Table {
@override
String get tableName => 'manager_workoutlog';

View File

@@ -0,0 +1,42 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2020, 2025 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:connectivity_plus/connectivity_plus.dart';
Future<bool> hasNetworkConnection({Duration timeout = const Duration(seconds: 1)}) async {
final connectivityResult = await Connectivity().checkConnectivity();
final allowed = [
ConnectivityResult.mobile,
ConnectivityResult.wifi,
ConnectivityResult.ethernet,
];
if (!connectivityResult.any((c) => allowed.contains(c))) {
return false;
}
return true;
// try {
// final result = await InternetAddress.lookup('example.com').timeout(timeout);
// return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
// } catch (_) {
// return false;
// }
}

View File

@@ -34,6 +34,7 @@ import 'package:wger/providers/measurement.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/routines.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/providers/wger_base_riverpod.dart';
import 'package:wger/screens/add_exercise_screen.dart';
import 'package:wger/screens/auth_screen.dart';
import 'package:wger/screens/configure_plates_screen.dart';
@@ -128,7 +129,7 @@ void main() async {
};
// Application
runApp(const riverpod.ProviderScope(child: MainApp()));
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
@@ -209,46 +210,56 @@ class MainApp extends StatelessWidget {
),
],
child: Consumer<AuthProvider>(
builder: (ctx, auth, _) => Consumer<UserProvider>(
builder: (ctx, user, _) => MaterialApp(
title: 'wger',
navigatorKey: navigatorKey,
theme: wgerLightTheme,
darkTheme: wgerDarkTheme,
highContrastTheme: wgerLightThemeHc,
highContrastDarkTheme: wgerDarkThemeHc,
themeMode: user.themeMode,
home: _getHomeScreen(auth),
routes: {
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
FormScreen.routeName: (ctx) => const FormScreen(),
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(),
MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(),
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
WeightScreen.routeName: (ctx) => const WeightScreen(),
RoutineScreen.routeName: (ctx) => const RoutineScreen(),
RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(),
WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(),
RoutineListScreen.routeName: (ctx) => const RoutineListScreen(),
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
AboutPage.routeName: (ctx) => const AboutPage(),
SettingsPage.routeName: (ctx) => const SettingsPage(),
LogOverviewPage.routeName: (ctx) => const LogOverviewPage(),
ConfigurePlatesScreen.routeName: (ctx) => const ConfigurePlatesScreen(),
},
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
),
),
builder: (ctx, auth, _) {
final baseInstance = WgerBaseProvider(Provider.of(ctx, listen: false));
return Consumer<UserProvider>(
builder: (ctx, user, _) => riverpod.ProviderScope(
overrides: [
wgerBaseProvider.overrideWithValue(baseInstance),
],
child: MaterialApp(
title: 'wger',
navigatorKey: navigatorKey,
theme: wgerLightTheme,
darkTheme: wgerDarkTheme,
highContrastTheme: wgerLightThemeHc,
highContrastDarkTheme: wgerDarkThemeHc,
themeMode: user.themeMode,
home: _getHomeScreen(auth),
routes: {
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
FormScreen.routeName: (ctx) => const FormScreen(),
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(),
MeasurementCategoriesScreen.routeName: (ctx) =>
const MeasurementCategoriesScreen(),
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
WeightScreen.routeName: (ctx) => const WeightScreen(),
RoutineScreen.routeName: (ctx) => const RoutineScreen(),
RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(),
WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(),
RoutineListScreen.routeName: (ctx) => const RoutineListScreen(),
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
AboutPage.routeName: (ctx) => const AboutPage(),
SettingsPage.routeName: (ctx) => const SettingsPage(),
LogOverviewPage.routeName: (ctx) => const LogOverviewPage(),
ConfigurePlatesScreen.routeName: (ctx) => const ConfigurePlatesScreen(),
},
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
),
),
);
},
),
);
}

View File

@@ -27,12 +27,13 @@ import 'package:wger/models/exercises/muscle.dart';
part 'exercise_data.g.dart';
@riverpod
@Riverpod(keepAlive: true)
final class ExerciseNotifier extends _$ExerciseNotifier {
final _logger = Logger('ExerciseNotifier');
@override
Stream<List<Exercise>> build() {
ref.keepAlive();
final db = ref.read(driftPowerSyncDatabase);
_logger.fine('Building exercise stream');

View File

@@ -20,7 +20,7 @@ final class ExerciseNotifierProvider
argument: null,
retry: null,
name: r'exerciseProvider',
isAutoDispose: true,
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@@ -33,7 +33,7 @@ final class ExerciseNotifierProvider
ExerciseNotifier create() => ExerciseNotifier();
}
String _$exerciseNotifierHash() => r'b8db6a2b5e05f4ba3f7a7677999816298209f5db';
String _$exerciseNotifierHash() => r'897551df3c53c72427a050c9559647aa9f8b59dd';
abstract class _$ExerciseNotifier extends $StreamNotifier<List<Exercise>> {
Stream<List<Exercise>> build();

View File

@@ -16,22 +16,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/exercises/category.dart';
import 'package:wger/models/exercises/equipment.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/providers/exercise_data.dart';
import 'package:wger/providers/exercise_state.dart';
import 'package:wger/providers/wger_base_riverpod.dart';
part 'exercise_state_notifier.g.dart';
@riverpod
@Riverpod(keepAlive: true)
final class ExerciseStateNotifier extends _$ExerciseStateNotifier {
final _logger = Logger('ExerciseStateNotifier');
static const exerciseInfoUrlPath = 'exerciseinfo';
@override
ExerciseState build() {
_logger.finer('Building ExerciseStateNotifier');
state = ExerciseState(isLoading: true);
// Load exercises
@@ -104,16 +111,20 @@ final class ExerciseStateNotifier extends _$ExerciseStateNotifier {
}
if (updated) {
setFilters(filters);
setFilters(filters, LANGUAGE_SHORT_ENGLISH);
}
}
void setFilters(Filters filters) {
final filtered = _applyFilters(state.exercises, filters);
void setFilters(Filters filters, [String languageCode = LANGUAGE_SHORT_ENGLISH]) {
final filtered = _applyFilters(state.exercises, filters, languageCode: languageCode);
state = state.copyWith(filters: filters, filteredExercises: filtered);
}
List<Exercise> _applyFilters(List<Exercise> all, Filters filters) {
List<Exercise> _applyFilters(
List<Exercise> all,
Filters filters, {
String languageCode = LANGUAGE_SHORT_ENGLISH,
}) {
if (filters.isNothingMarked && (filters.searchTerm.length <= 1)) {
return all;
}
@@ -123,8 +134,7 @@ final class ExerciseStateNotifier extends _$ExerciseStateNotifier {
final term = filters.searchTerm.toLowerCase();
items = items.where((e) {
// TODO: FullTextSearch?
// TODO!!!! translations
final title = (e.getTranslation('en').name ?? '').toLowerCase();
final title = (e.getTranslation(languageCode).name ?? '').toLowerCase();
return title.contains(term);
}).toList();
}
@@ -150,40 +160,89 @@ final class ExerciseStateNotifier extends _$ExerciseStateNotifier {
Future<List<Exercise>> searchExercise(
String term, {
bool useServer = false,
bool useOnlineSearch = true,
String languageCode = 'en',
bool searchEnglish = false,
}) async {
if (term.trim().length <= 1) {
if (term.isEmpty) {
return [];
}
// Local mode: search in the sqlite db
if (!useServer) {
final languages = [languageCode];
if (searchEnglish && languageCode != 'en') {
languages.add('en');
}
final searchMethod = useOnlineSearch ? searchExerciseOnline : searchExerciseLocal;
return searchMethod(
term,
languageCode: languageCode,
searchEnglish: searchEnglish,
);
}
final List<Exercise> out = [];
Future<List<Exercise>> searchExerciseOnline(
String term, {
String languageCode = 'en',
bool searchEnglish = false,
}) async {
final wgerBase = ref.read(wgerBaseProvider);
_logger.info('Online search for exercises: $term');
for (final e in state.exercises) {
var matched = false;
for (final lang in languages) {
final title = (e.getTranslation(lang).name ?? '').toLowerCase();
if (title.contains(term.toLowerCase())) {
matched = true;
break;
}
}
if (matched) {
out.add(e);
}
}
return out;
if (term.length <= 1) {
return [];
}
// Online mode, use server-side search API
return [];
final languages = [languageCode];
if (searchEnglish && languageCode != LANGUAGE_SHORT_ENGLISH) {
languages.add(LANGUAGE_SHORT_ENGLISH);
}
// Send the request
final result = await wgerBase.fetch(
wgerBase.makeUrl(
exerciseInfoUrlPath,
query: {
'name__search': term,
'language__code': languages.join(','),
'limit': API_RESULTS_PAGE_SIZE,
},
),
);
// Load the exercises
final ids = (result['results'] as List).map<int>((data) => data['id'] as int).toList();
// TODO: fix this! Why is the ref of stateProvider always disposed?
return state.exercises.where((e) => ids.contains(e.id)).toList();
return (result['results'] as List)
.map((data) => Exercise.fromApiDataJson(data as Map<String, dynamic>, []))
.toList();
}
Future<List<Exercise>> searchExerciseLocal(
String term, {
String languageCode = 'en',
bool searchEnglish = false,
}) async {
_logger.fine('Local search for exercises: $term');
final languages = [languageCode];
if (searchEnglish && languageCode != 'en') {
languages.add('en');
}
final List<Exercise> out = [];
for (final e in state.exercises) {
var matched = false;
for (final lang in languages) {
final title = (e.getTranslation(lang).name ?? '').toLowerCase();
if (title.contains(term.toLowerCase())) {
matched = true;
break;
}
}
if (matched) {
out.add(e);
}
}
return out;
}
}

View File

@@ -20,7 +20,7 @@ final class ExerciseStateNotifierProvider
argument: null,
retry: null,
name: r'exerciseStateProvider',
isAutoDispose: true,
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@@ -41,7 +41,7 @@ final class ExerciseStateNotifierProvider
}
}
String _$exerciseStateNotifierHash() => r'c3070d53059705b03595597f099ded2b7efb5a93';
String _$exerciseStateNotifierHash() => r'0190aca10f979de44254c429212e8a9e614cb167';
abstract class _$ExerciseStateNotifier extends $Notifier<ExerciseState> {
ExerciseState build();

View File

@@ -49,7 +49,6 @@ class ExercisesProvider with ChangeNotifier {
static const exerciseUrlPath = 'exercise';
static const exerciseInfoUrlPath = 'exerciseinfo';
static const exerciseSearchPath = 'exercise/search';
static const categoriesUrlPath = 'exercisecategory';
static const musclesUrlPath = 'muscle';
@@ -557,47 +556,4 @@ class ExercisesProvider with ChangeNotifier {
await prefs.setString(PREFS_LAST_UPDATED_EQUIPMENT, validTill.toIso8601String());
_logger.fine('Saved ${_equipment.length} equipment entries to cache (valid till $validTill)');
}
/// Searches for an exercise
///
/// We could do this locally, but the server has better text searching capabilities
/// with postgresql.
Future<List<Exercise>> searchExercise(
String name, {
String languageCode = LANGUAGE_SHORT_ENGLISH,
bool searchEnglish = false,
}) async {
if (name.length <= 1) {
return [];
}
final languages = [languageCode];
if (searchEnglish && languageCode != LANGUAGE_SHORT_ENGLISH) {
languages.add(LANGUAGE_SHORT_ENGLISH);
}
// Send the request
final result = await baseProvider.fetch(
baseProvider.makeUrl(
exerciseSearchPath,
query: {'term': name, 'language': languages.join(',')},
),
);
// Load the exercises
final results = ExerciseApiSearch.fromJson(result);
final List<Exercise> out = [];
for (final result in results.suggestions) {
final exercise = await fetchAndSetExercise(result.data.exerciseId);
if (exercise != null) {
out.add(exercise);
}
}
// return Future.wait(
// results.suggestions.map((e) => fetchAndSetExercise(e.data.exerciseId)),
// );
return out;
}
}

View File

@@ -1,4 +1,20 @@
// filepath: /Users/roland/Entwicklung/wger/flutter/lib/providers/wger_base_riverpod.dart
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2025 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:wger/providers/auth.dart';
@@ -8,6 +24,8 @@ import 'package:wger/providers/base_provider.dart';
/// to a [WgerBaseProvider] used by repositories.
///
/// Usage: ref.watch(wgerBaseProvider(authProvider))
final wgerBaseProvider = Provider.family<WgerBaseProvider, AuthProvider>(
(ref, auth) => WgerBaseProvider(auth),
);
final wgerBaseProvider = Provider<WgerBaseProvider>((ref) {
throw UnimplementedError(
'Override wgerBaseProvider in a ProviderScope with your existing WgerBaseProvider instance',
);
});

View File

@@ -25,6 +25,8 @@ import 'package:rive/rive.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/powersync/connector.dart';
import 'package:wger/providers/auth.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/exercise_state_notifier.dart';
import 'package:wger/providers/exercises.dart';
import 'package:wger/providers/gallery.dart';
import 'package:wger/providers/measurement.dart';
@@ -120,7 +122,8 @@ class _HomeTabsScreenState extends riverpod.ConsumerState<HomeTabsScreen>
nutritionPlansProvider.fetchIngredientsFromCache(),
exercisesProvider.fetchAndSetInitialData(),
]);
exercisesProvider.fetchAndSetAllExercises();
ref.read(exerciseStateProvider);
ref.read(weightEntryProvider());
// Workaround for https://github.com/wger-project/flutter/issues/901
// It seems that it can happen that sometimes the units were not loaded properly

View File

@@ -1,30 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/connectivity.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/providers/exercises.dart';
import 'package:wger/providers/exercise_state_notifier.dart';
import 'package:wger/screens/add_exercise_screen.dart';
import 'package:wger/widgets/exercises/images.dart';
typedef ExerciseSelectedCallback = void Function(Exercise exercise);
class ExerciseAutocompleter extends StatefulWidget {
class ExerciseAutocompleter extends ConsumerStatefulWidget {
final ExerciseSelectedCallback onExerciseSelected;
const ExerciseAutocompleter({required this.onExerciseSelected});
@override
State<ExerciseAutocompleter> createState() => _ExerciseAutocompleterState();
ConsumerState<ExerciseAutocompleter> createState() => _ExerciseAutocompleterState();
}
class _ExerciseAutocompleterState extends State<ExerciseAutocompleter> {
class _ExerciseAutocompleterState extends ConsumerState<ExerciseAutocompleter> {
bool _searchEnglish = true;
final _exercisesController = TextEditingController();
@override
Widget build(BuildContext context) {
final languageCode = Localizations.localeOf(context).languageCode;
return Column(
// mainAxisSize: MainAxisSize.min,
children: [
@@ -80,35 +83,37 @@ class _ExerciseAutocompleterState extends State<ExerciseAutocompleter> {
),
);
},
suggestionsCallback: (pattern) {
if (pattern == '') {
suggestionsCallback: (pattern) async {
if (pattern.isEmpty) {
return null;
}
return context.read<ExercisesProvider>().searchExercise(
pattern,
languageCode: Localizations.localeOf(context).languageCode,
searchEnglish: _searchEnglish,
);
return ref
.read(exerciseStateProvider.notifier)
.searchExercise(
pattern,
languageCode: languageCode,
searchEnglish: _searchEnglish,
useOnlineSearch: await hasNetworkConnection(),
);
},
itemBuilder:
(
BuildContext context,
Exercise exerciseSuggestion,
Exercise exercise,
) => ListTile(
key: Key('exercise-${exerciseSuggestion.id}'),
key: Key('exercise-${exercise.id}'),
leading: SizedBox(
width: 45,
child: ExerciseImageWidget(
image: exerciseSuggestion.getMainImage,
image: exercise.getMainImage,
),
),
title: Text(
exerciseSuggestion
.getTranslation(Localizations.localeOf(context).languageCode)
.name,
exercise.getTranslation(languageCode).name,
),
subtitle: Text(
'${exerciseSuggestion.category!.name} / ${exerciseSuggestion.equipment.map((e) => e.name).join(', ')}',
'${exercise.category!.name} / ${exercise.equipment.map((e) => e.name).join(', ')}',
),
),
emptyBuilder: (context) {
@@ -141,7 +146,7 @@ class _ExerciseAutocompleterState extends State<ExerciseAutocompleter> {
_exercisesController.text = '';
},
),
if (Localizations.localeOf(context).languageCode != LANGUAGE_SHORT_ENGLISH)
if (languageCode != LANGUAGE_SHORT_ENGLISH)
SwitchListTile(
title: Text(AppLocalizations.of(context).searchNamesInEnglish),
value: _searchEnglish,

View File

@@ -71,7 +71,12 @@ class _ExerciseFilterModalBodyState extends ConsumerState<ExerciseFilterModalBod
onChanged: (_) {
setState(() {
filterCategory.items.update(currentEntry.key, (value) => !value);
ref.read(exerciseStateProvider.notifier).setFilters(filters);
ref
.read(exerciseStateProvider.notifier)
.setFilters(
filters,
Localizations.localeOf(context).languageCode,
);
});
},
);

View File

@@ -49,6 +49,7 @@ class _FilterRowState extends ConsumerState<FilterRow> {
.read(exerciseStateProvider.notifier)
.setFilters(
currentFilters.copyWith(searchTerm: text),
Localizations.localeOf(context).languageCode,
);
}
});

View File

@@ -132,7 +132,7 @@ class _GymModeState extends ConsumerState<GymMode> {
List<Widget> getContent() {
final state = ref.watch(gymStateProvider);
final exercisesAsync = ref.watch(exerciseStateProvider.notifier);
final exercisesAsync = ref.read(exerciseStateProvider.notifier);
final routinesProvider = context.read<RoutinesProvider>();
var currentElement = 1;
final List<Widget> out = [];

View File

@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import connectivity_plus
import file_selector_macos
import package_info_plus
import path_provider_foundation
@@ -16,6 +17,7 @@ import url_launcher_macos
import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@@ -257,6 +257,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert:
dependency: transitive
description:
@@ -329,6 +345,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
drift:
dependency: "direct main"
description:
@@ -940,6 +964,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble:
dependency: transitive
description:

View File

@@ -73,6 +73,7 @@ dependencies:
riverpod_annotation: ^3.0.3
stream_transform: ^2.1.1
state_notifier: ^1.0.0
connectivity_plus: ^7.0.0
dev_dependencies:
flutter_test:

View File

@@ -155,14 +155,6 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
)
as List<_i4.Exercise>);
@override
List<_i4.Exercise> get filteredExercises =>
(super.noSuchMethod(
Invocation.getter(#filteredExercises),
returnValue: <_i4.Exercise>[],
)
as List<_i4.Exercise>);
@override
Map<int, List<_i4.Exercise>> get exerciseByVariation =>
(super.noSuchMethod(
@@ -215,12 +207,6 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
returnValueForMissingStub: null,
);
@override
set filteredExercises(List<_i4.Exercise>? newFilteredExercises) => super.noSuchMethod(
Invocation.setter(#filteredExercises, newFilteredExercises),
returnValueForMissingStub: null,
);
@override
set languages(List<_i8.Language>? languages) => super.noSuchMethod(
Invocation.setter(#languages, languages),
@@ -231,30 +217,6 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
@override
_i18.Future<void> setFilters(_i17.Filters? newFilters) =>
(super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: _i18.Future<void>.value(),
returnValueForMissingStub: _i18.Future<void>.value(),
)
as _i18.Future<void>);
@override
void initFilters() => super.noSuchMethod(
Invocation.method(#initFilters, []),
returnValueForMissingStub: null,
);
@override
_i18.Future<void> findByFilters() =>
(super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: _i18.Future<void>.value(),
returnValueForMissingStub: _i18.Future<void>.value(),
)
as _i18.Future<void>);
@override
void clear() => super.noSuchMethod(
Invocation.method(#clear, []),
@@ -496,24 +458,6 @@ class MockExercisesProvider extends _i1.Mock implements _i17.ExercisesProvider {
)
as _i18.Future<void>);
@override
_i18.Future<List<_i4.Exercise>> searchExercise(
String? name, {
String? languageCode = 'en',
bool? searchEnglish = false,
}) =>
(super.noSuchMethod(
Invocation.method(
#searchExercise,
[name],
{#languageCode: languageCode, #searchEnglish: searchEnglish},
),
returnValue: _i18.Future<List<_i4.Exercise>>.value(
<_i4.Exercise>[],
),
)
as _i18.Future<List<_i4.Exercise>>);
@override
void addListener(_i19.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#addListener, [listener]),

View File

@@ -512,14 +512,6 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
)
as List<_i6.Exercise>);
@override
List<_i6.Exercise> get filteredExercises =>
(super.noSuchMethod(
Invocation.getter(#filteredExercises),
returnValue: <_i6.Exercise>[],
)
as List<_i6.Exercise>);
@override
Map<int, List<_i6.Exercise>> get exerciseByVariation =>
(super.noSuchMethod(
@@ -572,12 +564,6 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
returnValueForMissingStub: null,
);
@override
set filteredExercises(List<_i6.Exercise>? newFilteredExercises) => super.noSuchMethod(
Invocation.setter(#filteredExercises, newFilteredExercises),
returnValueForMissingStub: null,
);
@override
set languages(List<_i10.Language>? languages) => super.noSuchMethod(
Invocation.setter(#languages, languages),
@@ -588,30 +574,6 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
@override
_i15.Future<void> setFilters(_i20.Filters? newFilters) =>
(super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: _i15.Future<void>.value(),
returnValueForMissingStub: _i15.Future<void>.value(),
)
as _i15.Future<void>);
@override
void initFilters() => super.noSuchMethod(
Invocation.method(#initFilters, []),
returnValueForMissingStub: null,
);
@override
_i15.Future<void> findByFilters() =>
(super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: _i15.Future<void>.value(),
returnValueForMissingStub: _i15.Future<void>.value(),
)
as _i15.Future<void>);
@override
void clear() => super.noSuchMethod(
Invocation.method(#clear, []),
@@ -853,24 +815,6 @@ class MockExercisesProvider extends _i1.Mock implements _i20.ExercisesProvider {
)
as _i15.Future<void>);
@override
_i15.Future<List<_i6.Exercise>> searchExercise(
String? name, {
String? languageCode = 'en',
bool? searchEnglish = false,
}) =>
(super.noSuchMethod(
Invocation.method(
#searchExercise,
[name],
{#languageCode: languageCode, #searchEnglish: searchEnglish},
),
returnValue: _i15.Future<List<_i6.Exercise>>.value(
<_i6.Exercise>[],
),
)
as _i15.Future<List<_i6.Exercise>>);
@override
void addListener(_i16.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#addListener, [listener]),

View File

@@ -99,14 +99,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
)
as List<_i4.Exercise>);
@override
List<_i4.Exercise> get filteredExercises =>
(super.noSuchMethod(
Invocation.getter(#filteredExercises),
returnValue: <_i4.Exercise>[],
)
as List<_i4.Exercise>);
@override
Map<int, List<_i4.Exercise>> get exerciseByVariation =>
(super.noSuchMethod(
@@ -159,12 +151,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
returnValueForMissingStub: null,
);
@override
set filteredExercises(List<_i4.Exercise>? newFilteredExercises) => super.noSuchMethod(
Invocation.setter(#filteredExercises, newFilteredExercises),
returnValueForMissingStub: null,
);
@override
set languages(List<_i8.Language>? languages) => super.noSuchMethod(
Invocation.setter(#languages, languages),
@@ -175,30 +161,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
@override
_i10.Future<void> setFilters(_i9.Filters? newFilters) =>
(super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: _i10.Future<void>.value(),
returnValueForMissingStub: _i10.Future<void>.value(),
)
as _i10.Future<void>);
@override
void initFilters() => super.noSuchMethod(
Invocation.method(#initFilters, []),
returnValueForMissingStub: null,
);
@override
_i10.Future<void> findByFilters() =>
(super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: _i10.Future<void>.value(),
returnValueForMissingStub: _i10.Future<void>.value(),
)
as _i10.Future<void>);
@override
void clear() => super.noSuchMethod(
Invocation.method(#clear, []),
@@ -440,24 +402,6 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
)
as _i10.Future<void>);
@override
_i10.Future<List<_i4.Exercise>> searchExercise(
String? name, {
String? languageCode = 'en',
bool? searchEnglish = false,
}) =>
(super.noSuchMethod(
Invocation.method(
#searchExercise,
[name],
{#languageCode: languageCode, #searchEnglish: searchEnglish},
),
returnValue: _i10.Future<List<_i4.Exercise>>.value(
<_i4.Exercise>[],
),
)
as _i10.Future<List<_i4.Exercise>>);
@override
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#addListener, [listener]),

View File

@@ -243,14 +243,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider {
)
as List<_i6.Exercise>);
@override
List<_i6.Exercise> get filteredExercises =>
(super.noSuchMethod(
Invocation.getter(#filteredExercises),
returnValue: <_i6.Exercise>[],
)
as List<_i6.Exercise>);
@override
Map<int, List<_i6.Exercise>> get exerciseByVariation =>
(super.noSuchMethod(
@@ -303,12 +295,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider {
returnValueForMissingStub: null,
);
@override
set filteredExercises(List<_i6.Exercise>? newFilteredExercises) => super.noSuchMethod(
Invocation.setter(#filteredExercises, newFilteredExercises),
returnValueForMissingStub: null,
);
@override
set languages(List<_i10.Language>? languages) => super.noSuchMethod(
Invocation.setter(#languages, languages),
@@ -319,30 +305,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider {
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
@override
_i11.Future<void> setFilters(_i12.Filters? newFilters) =>
(super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
)
as _i11.Future<void>);
@override
void initFilters() => super.noSuchMethod(
Invocation.method(#initFilters, []),
returnValueForMissingStub: null,
);
@override
_i11.Future<void> findByFilters() =>
(super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
)
as _i11.Future<void>);
@override
void clear() => super.noSuchMethod(
Invocation.method(#clear, []),
@@ -584,24 +546,6 @@ class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider {
)
as _i11.Future<void>);
@override
_i11.Future<List<_i6.Exercise>> searchExercise(
String? name, {
String? languageCode = 'en',
bool? searchEnglish = false,
}) =>
(super.noSuchMethod(
Invocation.method(
#searchExercise,
[name],
{#languageCode: languageCode, #searchEnglish: searchEnglish},
),
returnValue: _i11.Future<List<_i6.Exercise>>.value(
<_i6.Exercise>[],
),
)
as _i11.Future<List<_i6.Exercise>>);
@override
void addListener(_i13.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#addListener, [listener]),

View File

@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <powersync_flutter_libs/powersync_flutter_libs_plugin.h>
#include <rive_common/rive_plugin.h>
@@ -13,6 +14,8 @@
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
PowersyncFlutterLibsPluginRegisterWithRegistrar(

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
file_selector_windows
powersync_flutter_libs
rive_common