Skip to content

Commit 3e8da06

Browse files
authored
Merge pull request #1183 from wger-project/feature/offline-mode
Add offline mode and move the providers to riverpos
2 parents 0e6876d + 62042bd commit 3e8da06

619 files changed

Lines changed: 70142 additions & 39090 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/flutter-common/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ runs:
99
uses: subosito/flutter-action@v2
1010
with:
1111
channel: stable
12-
flutter-version: 3.41.9
12+
flutter-version: 3.44.0
1313
cache: true
1414

1515
- name: Install Flutter dependencies

.github/workflows/build-linux.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- name: Build application for linux
6262
run: |
6363
sudo apt update
64-
sudo apt install -y pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev --no-install-recommends
64+
sudo apt install -y pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev libsecret-1-dev libjsoncpp-dev --no-install-recommends
6565
flutter build linux --release
6666
tar -zcvf linux-${{ matrix.platform }}.tar.gz build/linux/${{ matrix.platform }}/release/bundle
6767
- uses: actions/upload-artifact@v7
@@ -92,7 +92,7 @@ jobs:
9292
git clone https://github.com/TheAppgineer/flatpak-flutter.git --branch 0.14.1 ../flatpak-flutter
9393
pip install -r ../flatpak-flutter/requirements.txt
9494
python bump-versions.py ${{ inputs.ref }} ${{ needs.build_linux.outputs.flutter_sdk_version }}
95-
../flatpak-flutter/flatpak-flutter.py --app-module wger flatpak-flutter.json
95+
../flatpak-flutter/flatpak-flutter.py --app-module wger flatpak-flutter.yml
9696
9797
- name: Push updated config to flathub repository
9898
env:

.github/workflows/screenshots.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ jobs:
2020
- name: Common flutter setup
2121
uses: ./.github/actions/flutter-common
2222

23-
- name: Install CocoaPods
24-
run: |
25-
cd ios
26-
pod install || true
27-
cd ..
28-
2923
- name: Boot iOS simulator
3024
id: boot
3125
run: |

android/app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@
4949
<action android:name="android.intent.action.MAIN" />
5050
<category android:name="android.intent.category.LAUNCHER" />
5151
</intent-filter>
52+
53+
<!-- Web-handoff deep link: the server redirects to wger://app-auth#token=…
54+
after a browser login; app_link_router consumes the URI. -->
55+
<intent-filter>
56+
<action android:name="android.intent.action.VIEW" />
57+
<category android:name="android.intent.category.DEFAULT" />
58+
<category android:name="android.intent.category.BROWSABLE" />
59+
<data android:scheme="wger" android:host="app-auth" />
60+
</intent-filter>
5261
</activity>
5362

5463

build.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
targets:
2+
$default:
3+
builders:
4+
drift_dev:
5+
options:
6+
store_date_time_values_as_text: true

devtools_options.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
extensions:
22
- drift: true
3-
- provider: true
3+
- provider: true
4+
- shared_preferences: true

flatpak/de.wger.flutter.desktop

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Comment=Fitness/workout, nutrition and weight tracker
88
# HealthFitness;Exercise;Nutrition
99
Categories=Education;Utility;Sports;
1010
Icon=de.wger.flutter
11-
Exec=wger
11+
Exec=wger %u
12+
MimeType=x-scheme-handler/wger;
1213
StartupWMClass=wger
1314
X-Purism-FormFactor=Workstation;Mobile;
1415
X-KDE-FormFactors=desktop;tablet;handset;mediacenter;

flatpak/de.wger.flutter.service

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[D-BUS Service]
2+
Name=de.wger.flutter
3+
Exec=/app/bin/wger --gapplication-service

integration_test/make_screenshots_test.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ enum DeviceType {
4444
androidWear('wearScreenshots'),
4545

4646
iOSPhoneBig('iPhone 6.9', isAndroid: false),
47-
iOSPhoneSmall('iPhone 6.7', isAndroid: false)
48-
;
47+
iOSPhoneSmall('iPhone 6.7', isAndroid: false);
4948

5049
final String folderName;
5150
final bool isAndroid;

integration_test/screenshots_01_dashboard.dart

Lines changed: 78 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -17,131 +17,112 @@
1717
*/
1818

1919
import 'package:flutter/material.dart';
20-
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
20+
import 'package:flutter_riverpod/flutter_riverpod.dart';
2121
import 'package:mockito/mockito.dart';
22-
import 'package:provider/provider.dart';
2322
import 'package:wger/l10n/generated/app_localizations.dart';
24-
import 'package:wger/models/workouts/session.dart';
25-
import 'package:wger/providers/auth.dart';
26-
import 'package:wger/providers/body_weight.dart';
27-
import 'package:wger/providers/exercises.dart';
28-
import 'package:wger/providers/gallery.dart';
29-
import 'package:wger/providers/measurement.dart';
30-
import 'package:wger/providers/nutrition.dart';
31-
import 'package:wger/providers/routines.dart';
32-
import 'package:wger/providers/user.dart';
23+
import 'package:wger/models/measurements/measurement_category.dart';
24+
import 'package:wger/providers/auth_notifier.dart';
25+
import 'package:wger/providers/auth_state.dart';
26+
import 'package:wger/providers/body_weight_repository.dart';
27+
import 'package:wger/providers/gallery_repository.dart';
28+
import 'package:wger/providers/ingredient_repository.dart';
29+
import 'package:wger/providers/measurement_repository.dart';
30+
import 'package:wger/providers/nutrition_notifier.dart';
31+
import 'package:wger/providers/nutrition_repository.dart';
32+
import 'package:wger/providers/routines_notifier.dart';
33+
import 'package:wger/providers/user_profile_repository.dart';
3334
import 'package:wger/screens/home_tabs_screen.dart';
3435
import 'package:wger/theme/theme.dart';
3536

36-
import '../test/exercises/contribute_exercise_test.mocks.dart';
3737
import '../test/gallery/gallery_form_test.mocks.dart';
3838
import '../test/measurements/measurement_categories_screen_test.mocks.dart';
3939
import '../test/nutrition/nutritional_plan_screen_test.mocks.dart';
40-
import '../test/routine/weight_unit_form_widget_test.mocks.dart';
4140
import '../test/weight/weight_screen_test.mocks.dart' as weight;
4241
import '../test_data/body_weight.dart';
4342
import '../test_data/exercises.dart';
43+
import '../test_data/gallery.dart';
4444
import '../test_data/measurements.dart';
4545
import '../test_data/nutritional_plans.dart';
4646
import '../test_data/profile.dart';
4747
import '../test_data/routines.dart';
4848

49-
Widget createDashboardScreen({Locale? locale}) {
50-
locale ??= const Locale('en');
49+
class _FakeAuthNotifier extends AuthNotifier {
50+
_FakeAuthNotifier(this._state);
5151

52-
final mockGalleryProvider = MockGalleryProvider();
52+
final AuthState _state;
5353

54-
final mockExercisesProvider = MockExercisesProvider();
54+
@override
55+
Future<AuthState> build() async => _state;
56+
}
5557

56-
final mockAuthProvider = MockAuthProvider();
57-
when(mockAuthProvider.setServerVersion()).thenAnswer((_) async {});
58-
when(mockAuthProvider.dataInit).thenReturn(true);
58+
Widget createDashboardScreen({Locale? locale}) {
59+
locale ??= const Locale('en');
5960

60-
final mockWorkoutProvider = MockRoutinesProvider();
61-
when(mockWorkoutProvider.items).thenReturn([getTestRoutine(exercises: getScreenshotExercises())]);
62-
when(
63-
mockWorkoutProvider.currentRoutine,
64-
).thenReturn(getTestRoutine(exercises: getScreenshotExercises()));
61+
final mockGalleryRepo = MockGalleryRepository();
62+
when(mockGalleryRepo.watchAllDrift()).thenAnswer((_) => Stream.value(getTestImages()));
6563

66-
when(mockWorkoutProvider.fetchSessionData()).thenAnswer(
67-
(a) => Future.value([
68-
WorkoutSession(
69-
routineId: 1,
70-
date: DateTime.now().add(const Duration(days: -1)),
71-
timeStart: const TimeOfDay(hour: 17, minute: 34),
72-
timeEnd: const TimeOfDay(hour: 19, minute: 3),
73-
impression: 3,
74-
),
75-
]),
76-
);
64+
final mockNutritionRepo = weight.MockNutritionRepository();
65+
final mockIngredientRepo = weight.MockIngredientRepository();
66+
when(mockIngredientRepo.getById(any)).thenAnswer((_) async => null);
7767

78-
final mockNutritionProvider = weight.MockNutritionPlansProvider();
68+
final mockBodyWeightRepository = MockBodyWeightRepository();
69+
when(
70+
mockBodyWeightRepository.watchAllDrift(),
71+
).thenAnswer((_) => Stream.value(getWeightEntries()));
7972

73+
final mockMeasurementRepo = MockMeasurementRepository();
8074
when(
81-
mockNutritionProvider.currentPlan,
82-
).thenAnswer((realInvocation) => getNutritionalPlanScreenshot());
83-
when(mockNutritionProvider.items).thenReturn([getNutritionalPlanScreenshot()]);
75+
mockMeasurementRepo.watchAll(),
76+
).thenAnswer((_) => Stream<List<MeasurementCategory>>.value(getMeasurementCategories()));
8477

85-
final mockWeightProvider = weight.MockBodyWeightProvider();
86-
when(mockWeightProvider.items).thenReturn(getScreenshotWeightEntries());
78+
final mockUserProfileRepo = weight.MockUserProfileRepository();
79+
when(
80+
mockUserProfileRepo.watchDrift(),
81+
).thenAnswer((_) => Stream.value(tUserProfile1));
8782

88-
final mockMeasurementProvider = MockMeasurementProvider();
89-
when(mockMeasurementProvider.categories).thenReturn(getMeasurementCategories());
83+
const loggedInAuth = AuthState(
84+
status: AuthStatus.loggedIn,
85+
credential: LegacyCredential('test-token'),
86+
serverUrl: 'http://localhost',
87+
);
88+
final container = ProviderContainer.test(
89+
overrides: [
90+
bodyWeightRepositoryProvider.overrideWithValue(mockBodyWeightRepository),
91+
measurementRepositoryProvider.overrideWithValue(mockMeasurementRepo),
92+
authProvider.overrideWith(() => _FakeAuthNotifier(loggedInAuth)),
93+
userProfileRepositoryProvider.overrideWithValue(mockUserProfileRepo),
94+
nutritionRepositoryProvider.overrideWithValue(mockNutritionRepo),
95+
ingredientRepositoryProvider.overrideWithValue(mockIngredientRepo),
96+
galleryRepositoryProvider.overrideWithValue(mockGalleryRepo),
97+
],
98+
);
99+
container.read(routinesRiverpodProvider.notifier).state = AsyncData(
100+
RoutinesState(
101+
routines: [getTestRoutine(exercises: getScreenshotExercises())],
102+
),
103+
);
90104

91-
final mockUserProvider = MockUserProvider();
92-
when(mockUserProvider.profile).thenReturn(tProfile1);
93-
when(mockUserProvider.dashboardWidgets).thenReturn([
94-
DashboardWidget.routines,
95-
DashboardWidget.weight,
96-
DashboardWidget.measurements,
97-
DashboardWidget.calendar,
98-
DashboardWidget.nutrition,
99-
DashboardWidget.trophies,
100-
]);
105+
// Seed the nutrition notifier with the screenshot plan so the dashboard can
106+
// show it without going through the server.
107+
container.read(nutritionProvider.notifier).state = AsyncData(
108+
NutritionState(plans: [getNutritionalPlanScreenshot()]),
109+
);
101110

102-
return riverpod.ProviderScope(
103-
child: MediaQuery(
104-
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first)
105-
.copyWith(
106-
padding: EdgeInsets.zero,
107-
viewPadding: EdgeInsets.zero,
108-
viewInsets: EdgeInsets.zero,
109-
),
110-
child: MultiProvider(
111-
providers: [
112-
ChangeNotifierProvider<GalleryProvider>(
113-
create: (context) => mockGalleryProvider,
114-
),
115-
ChangeNotifierProvider<ExercisesProvider>(
116-
create: (context) => mockExercisesProvider,
117-
),
118-
ChangeNotifierProvider<AuthProvider>(
119-
create: (context) => mockAuthProvider,
120-
),
121-
ChangeNotifierProvider<UserProvider>(
122-
create: (context) => mockUserProvider,
123-
),
124-
ChangeNotifierProvider<RoutinesProvider>(
125-
create: (context) => mockWorkoutProvider,
126-
),
127-
ChangeNotifierProvider<NutritionPlansProvider>(
128-
create: (context) => mockNutritionProvider,
129-
),
130-
ChangeNotifierProvider<BodyWeightProvider>(
131-
create: (context) => mockWeightProvider,
132-
),
133-
ChangeNotifierProvider<MeasurementProvider>(
134-
create: (context) => mockMeasurementProvider,
135-
),
136-
],
137-
child: MaterialApp(
138-
locale: locale,
139-
debugShowCheckedModeBanner: false,
140-
localizationsDelegates: AppLocalizations.localizationsDelegates,
141-
supportedLocales: AppLocalizations.supportedLocales,
142-
theme: wgerLightTheme,
143-
home: HomeTabsScreen(),
144-
),
111+
return MediaQuery(
112+
data: MediaQueryData.fromView(WidgetsBinding.instance.platformDispatcher.views.first).copyWith(
113+
padding: EdgeInsets.zero,
114+
viewPadding: EdgeInsets.zero,
115+
viewInsets: EdgeInsets.zero,
116+
),
117+
child: UncontrolledProviderScope(
118+
container: container,
119+
child: MaterialApp(
120+
locale: locale,
121+
debugShowCheckedModeBanner: false,
122+
localizationsDelegates: AppLocalizations.localizationsDelegates,
123+
supportedLocales: AppLocalizations.supportedLocales,
124+
theme: wgerLightTheme,
125+
home: HomeTabsScreen(),
145126
),
146127
),
147128
);

0 commit comments

Comments
 (0)