Merge remote-tracking branch 'origin/master'

# Conflicts:
#	test/weight/weight_screen_test.dart
This commit is contained in:
Roland Geider
2024-01-23 16:42:25 +01:00
10 changed files with 195 additions and 31 deletions

View File

@@ -785,7 +785,7 @@
"@settingsCacheDescription": {},
"settingsCacheDeletedSnackbar": "Zwischenspeicher erfolgreich gelöscht",
"@settingsCacheDeletedSnackbar": {},
"lb": "Pfund",
"lb": "lb",
"@lb": {
"description": "Generated entry for translation for server strings"
}

View File

@@ -167,6 +167,7 @@
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"useMetric": "Use metric units for body weight",
"weightUnit": "Weight unit",
"@weightUnit": {},
"repetitionUnit": "Repetition unit",

View File

@@ -31,6 +31,11 @@ class Profile {
@JsonKey(required: true, name: 'is_trustworthy')
bool isTrustworthy;
@JsonKey(required: true, name: 'weight_unit')
String weightUnitStr;
bool get isMetric => weightUnitStr == 'kg';
@JsonKey(required: true)
String email;
@@ -39,9 +44,11 @@ class Profile {
required this.emailVerified,
required this.isTrustworthy,
required this.email,
required this.weightUnitStr,
});
// Boilerplate
factory Profile.fromJson(Map<String, dynamic> json) => _$ProfileFromJson(json);
Map<String, dynamic> toJson() => _$ProfileToJson(this);
}

View File

@@ -9,13 +9,14 @@ part of 'profile.dart';
Profile _$ProfileFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['username', 'email_verified', 'is_trustworthy', 'email'],
requiredKeys: const ['username', 'email_verified', 'is_trustworthy', 'weight_unit', 'email'],
);
return Profile(
username: json['username'] as String,
emailVerified: json['email_verified'] as bool,
isTrustworthy: json['is_trustworthy'] as bool,
email: json['email'] as String,
weightUnitStr: json['weight_unit'] as String,
);
}
@@ -23,5 +24,6 @@ Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{
'username': instance.username,
'email_verified': instance.emailVerified,
'is_trustworthy': instance.isTrustworthy,
'weight_unit': instance.weightUnitStr,
'email': instance.email,
};

View File

@@ -27,6 +27,7 @@ import 'package:wger/models/workouts/workout_plan.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/measurement.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/providers/workout_plans.dart';
import 'package:wger/screens/form_screen.dart';
import 'package:wger/screens/gym_mode.dart';
@@ -243,6 +244,7 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
@override
Widget build(BuildContext context) {
final profile = context.read<UserProvider>().profile;
weightEntriesData = Provider.of<BodyWeightProvider>(context, listen: false);
return Consumer<BodyWeightProvider>(
@@ -267,9 +269,13 @@ class _DashboardWeightWidgetState extends State<DashboardWeightWidget> {
children: [
SizedBox(
height: 200,
child: MeasurementChartWidgetFl(weightEntriesData.items
.map((e) => MeasurementChartEntry(e.weight, e.date))
.toList()),
child: MeasurementChartWidgetFl(
weightEntriesData.items
.map((e) => MeasurementChartEntry(e.weight, e.date))
.toList(),
unit: profile!.isMetric
? AppLocalizations.of(context).kg
: AppLocalizations.of(context).lb),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@@ -23,14 +23,26 @@ import 'package:wger/models/user/profile.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/theme/theme.dart';
class UserProfileForm extends StatelessWidget {
class UserProfileForm extends StatefulWidget {
late final Profile _profile;
final _form = GlobalKey<FormState>();
final emailController = TextEditingController();
UserProfileForm(Profile profile) {
_profile = profile;
emailController.text = _profile.email;
}
@override
State<UserProfileForm> createState() => _UserProfileFormState();
}
class _UserProfileFormState extends State<UserProfileForm> {
final _form = GlobalKey<FormState>();
final emailController = TextEditingController();
@override
void initState() {
super.initState();
emailController.text = widget._profile.email;
}
@override
@@ -42,16 +54,29 @@ class UserProfileForm extends StatelessWidget {
ListTile(
leading: const Icon(Icons.person, color: wgerPrimaryColor),
title: Text(AppLocalizations.of(context).username),
subtitle: Text(_profile.username),
subtitle: Text(widget._profile.username),
),
SwitchListTile(
title: Text(AppLocalizations.of(context).useMetric),
subtitle: Text(widget._profile.weightUnitStr),
value: widget._profile.isMetric,
onChanged: (_) {
setState(() {
widget._profile.weightUnitStr = widget._profile.isMetric
? AppLocalizations.of(context).lb
: AppLocalizations.of(context).kg;
});
},
dense: true,
),
ListTile(
leading: const Icon(Icons.email_rounded, color: wgerPrimaryColor),
title: TextFormField(
decoration: InputDecoration(
labelText: _profile.emailVerified
labelText: widget._profile.emailVerified
? AppLocalizations.of(context).verifiedEmail
: AppLocalizations.of(context).unVerifiedEmail,
suffixIcon: _profile.emailVerified
suffixIcon: widget._profile.emailVerified
? const Icon(
Icons.check_circle,
color: Colors.green,
@@ -60,7 +85,7 @@ class UserProfileForm extends StatelessWidget {
controller: emailController,
keyboardType: TextInputType.emailAddress,
onSaved: (newValue) {
_profile.email = newValue!;
widget._profile.email = newValue!;
},
validator: (value) {
if (value!.isNotEmpty && !value.contains('@')) {
@@ -70,11 +95,11 @@ class UserProfileForm extends StatelessWidget {
},
),
),
if (!_profile.emailVerified)
if (!widget._profile.emailVerified)
OutlinedButton(
onPressed: () async {
// Email is already verified
if (_profile.emailVerified) {
if (widget._profile.emailVerified) {
return;
}
@@ -83,7 +108,7 @@ class UserProfileForm extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).verifiedEmailInfo(_profile.email),
AppLocalizations.of(context).verifiedEmailInfo(widget._profile.email),
),
),
);
@@ -91,9 +116,6 @@ class UserProfileForm extends StatelessWidget {
child: Text(AppLocalizations.of(context).verify),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: wgerPrimaryButtonColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50))),
onPressed: () async {
// Validate and save the current values to the weightEntry
final isValid = _form.currentState!.validate();

View File

@@ -21,6 +21,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/screens/form_screen.dart';
import 'package:wger/screens/measurement_categories_screen.dart';
import 'package:wger/widgets/measurements/charts.dart';
@@ -29,6 +30,7 @@ import 'package:wger/widgets/weight/forms.dart';
class WeightEntriesList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final profile = context.read<UserProvider>().profile;
final weightProvider = Provider.of<BodyWeightProvider>(context, listen: false);
return Column(
@@ -37,7 +39,11 @@ class WeightEntriesList extends StatelessWidget {
padding: const EdgeInsets.all(15),
height: 220,
child: MeasurementChartWidgetFl(
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList()),
weightProvider.items.map((e) => MeasurementChartEntry(e.weight, e.date)).toList(),
unit: profile!.isMetric
? AppLocalizations.of(context).kg
: AppLocalizations.of(context).lb,
),
),
TextButton(
onPressed: () => Navigator.pushNamed(

View File

@@ -23,34 +23,43 @@ import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/screens/form_screen.dart';
import 'package:wger/screens/weight_screen.dart';
import 'package:wger/widgets/measurements/charts.dart';
import 'package:wger/widgets/weight/forms.dart';
import '../../test_data/body_weight.dart';
import '../../test_data/profile.dart';
import 'weight_screen_test.mocks.dart';
@GenerateMocks([BodyWeightProvider])
@GenerateMocks([BodyWeightProvider, UserProvider])
void main() {
late MockBodyWeightProvider mockWeightProvider;
late MockUserProvider mockUserProvider;
setUp(() {
mockWeightProvider = MockBodyWeightProvider();
when(mockWeightProvider.items).thenReturn(getWeightEntries());
mockUserProvider = MockUserProvider();
when(mockUserProvider.profile).thenReturn(tProfile1);
});
Widget createWeightScreen({locale = 'en'}) {
return ChangeNotifierProvider<BodyWeightProvider>(
create: (context) => mockWeightProvider,
child: MaterialApp(
locale: Locale(locale),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: WeightScreen(),
routes: {
FormScreen.routeName: (_) => FormScreen(),
},
return ChangeNotifierProvider<UserProvider>(
create: (context) => mockUserProvider,
child: ChangeNotifierProvider<BodyWeightProvider>(
create: (context) => mockWeightProvider,
child: MaterialApp(
locale: Locale(locale),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: WeightScreen(),
routes: {
FormScreen.routeName: (_) => FormScreen(),
},
),
),
);
}

View File

@@ -8,8 +8,10 @@ import 'dart:ui' as _i6;
import 'package:mockito/mockito.dart' as _i1;
import 'package:wger/models/body_weight/weight_entry.dart' as _i3;
import 'package:wger/models/user/profile.dart' as _i8;
import 'package:wger/providers/base_provider.dart' as _i2;
import 'package:wger/providers/body_weight.dart' as _i4;
import 'package:wger/providers/user.dart' as _i7;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -192,3 +194,111 @@ class MockBodyWeightProvider extends _i1.Mock implements _i4.BodyWeightProvider
returnValueForMissingStub: null,
);
}
/// A class which mocks [UserProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockUserProvider extends _i1.Mock implements _i7.UserProvider {
MockUserProvider() {
_i1.throwOnMissingStub(this);
}
@override
_i2.WgerBaseProvider get baseProvider => (super.noSuchMethod(
Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0(
this,
Invocation.getter(#baseProvider),
),
) as _i2.WgerBaseProvider);
@override
set profile(_i8.Profile? _profile) => super.noSuchMethod(
Invocation.setter(
#profile,
_profile,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
void clear() => super.noSuchMethod(
Invocation.method(
#clear,
[],
),
returnValueForMissingStub: null,
);
@override
_i5.Future<void> fetchAndSetProfile() => (super.noSuchMethod(
Invocation.method(
#fetchAndSetProfile,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> saveProfile() => (super.noSuchMethod(
Invocation.method(
#saveProfile,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> verifyEmail() => (super.noSuchMethod(
Invocation.method(
#verifyEmail,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
void addListener(_i6.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
),
returnValueForMissingStub: null,
);
@override
void removeListener(_i6.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
),
returnValueForMissingStub: null,
);
@override
void dispose() => super.noSuchMethod(
Invocation.method(
#dispose,
[],
),
returnValueForMissingStub: null,
);
@override
void notifyListeners() => super.noSuchMethod(
Invocation.method(
#notifyListeners,
[],
),
returnValueForMissingStub: null,
);
}

View File

@@ -5,4 +5,5 @@ final tProfile1 = Profile(
emailVerified: true,
isTrustworthy: true,
email: 'admin@google.com',
weightUnitStr: 'kg',
);