Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions lib/core/utils/bounds/ranges_const.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:opennutritracker/core/utils/calc/unit_calc.dart';

class Ranges{
static test(){
}
//first_page
static const Duration maxDurationForBirthdayIntoTheFuture = Duration(days: -1); //values < 0 result a Date in the past
static const Duration maxAge = Duration(days: 365 * 130);
//second_page
static const double maxHeight = 300;
static const double minHeight = 30;
static const double maxWeight = 640;
static const double minWeight = 2;
static const bool cmAllowDecimalPlaces = false;
static const bool feetAllowDecimalPlaces = true;
static const bool kgAllowDecimalPlaces = true;
static const bool lbsAllowDecimalPlaces = true;

//generated
static final RegExp cmRegExp = cmAllowDecimalPlaces ? RegExp(r'^[0-9]+([.,][0-9])?$') : RegExp(r'^[0-9]+$');
static final RegExp feetRegExp = feetAllowDecimalPlaces ? RegExp(r'^[0-9]+([.,][0-9])?$') : RegExp(r'^[0-9]+$');
static final RegExp kgRegExp = kgAllowDecimalPlaces ? RegExp(r'^[0-9]+([.,][0-9])?$') : RegExp(r'^[0-9]+$');
static final RegExp lbsRegExp = lbsAllowDecimalPlaces ? RegExp(r'^[0-9]+([.,][0-9])?$') : RegExp(r'^[0-9]+$');
static final double maxHeightInFeet = UnitCalc.cmToFeet(maxHeight);
static final double minHeightInFeet = UnitCalc.cmToFeet(minHeight);
static final double maxWeightInLbs = UnitCalc.kgToLbs(maxWeight);
static final double minWeightInLbs = UnitCalc.kgToLbs(minWeight);
}
72 changes: 72 additions & 0 deletions lib/core/utils/bounds/validator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:opennutritracker/core/utils/bounds/ranges_const.dart';

import '../calc/unit_calc.dart';

class ValueValidator{

static String? heightStringValidator(String? value, String wrongHeightLabel, {bool isImperial = false}){
if(value == null) return wrongHeightLabel;

if (isImperial) {
if (value.isEmpty || !Ranges.feetRegExp.hasMatch(value)) {
return wrongHeightLabel;
} else {
return null;
}
} else {
if (value.isEmpty || !Ranges.cmRegExp.hasMatch(value)) {
return wrongHeightLabel;
} else {
return null;
}
}
}

static String? weightStringValidator(String? value, String wrongWeightLabel, {bool isImperial = false}){
if(value == null) return wrongWeightLabel;

if (isImperial) {
if (value.isEmpty || !Ranges.lbsRegExp.hasMatch(value)) {
return wrongWeightLabel;
} else {
return null;
}
} else {
if (value.isEmpty || !Ranges.kgRegExp.hasMatch(value)) {
return wrongWeightLabel;
} else {
return null;
}
}
}

static double? parseHeightInCm(double? height, {bool isImperial = false}){
if(height == null) return null;
bool isBelowMin = isImperial ? height < Ranges.minHeightInFeet : height < Ranges.minHeight;
bool isAboveMax = isImperial ? height > Ranges.maxHeightInFeet : height > Ranges.maxHeight;

if (isBelowMin || isAboveMax) {
return null;
}
return !isImperial ? height : UnitCalc.feetToCm(height);
}

static double? parseWeightInKg(double? weight, {bool isImperial = false}){
if(weight == null) return null;
bool isBelowMin = isImperial ? weight < Ranges.minWeightInLbs : weight < Ranges.minWeight;
bool isAboveMax = isImperial ? weight > Ranges.maxWeightInLbs : weight > Ranges.maxWeight;

if (isBelowMin || isAboveMax) {
return null;
}
return !isImperial ? weight : UnitCalc.lbsToKg(weight);
}

static DateTime getFirstDate(){
return DateTime.now().subtract(Ranges.maxAge);
}

static DateTime getLastDate(){
return DateTime.now().add(Ranges.maxDurationForBirthdayIntoTheFuture);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:opennutritracker/core/utils/bounds/validator.dart';
import 'package:opennutritracker/features/onboarding/domain/entity/user_gender_selection_entity.dart';
import 'package:opennutritracker/generated/l10n.dart';

Expand Down Expand Up @@ -86,11 +86,12 @@ class _OnboardingFirstPageBodyState extends State<OnboardingFirstPageBody> {
void onDateInputClicked() async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100));
initialDate: DateTime.now().compareTo(ValueValidator.getLastDate()) >= 0 ? ValueValidator.getLastDate() : DateTime.now(), // !!! if merge with #206, use both
firstDate: ValueValidator.getFirstDate(),
lastDate: ValueValidator.getLastDate());
if (pickedDate != null) {
String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
String formattedDate = localizations.formatCompactDate(pickedDate);
setState(() {
_selectedDate = pickedDate;
_dateInput.text = formattedDate;
Expand All @@ -106,11 +107,8 @@ class _OnboardingFirstPageBodyState extends State<OnboardingFirstPageBody> {
} else if (_femaleSelected) {
selectedGender = UserGenderSelectionEntity.genderFemale;
}

if (selectedGender != null && _selectedDate != null) {
widget.setPageContent(true, selectedGender, _selectedDate);
} else {
widget.setPageContent(false, null, null);
}
selectedGender != null && _selectedDate != null
? widget.setPageContent(true, selectedGender, _selectedDate)
: widget.setPageContent(false, null, null);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:opennutritracker/core/utils/calc/unit_calc.dart';
import 'package:opennutritracker/core/utils/bounds/validator.dart';
import 'package:opennutritracker/generated/l10n.dart';

class OnboardingSecondPageBody extends StatefulWidget {
Expand Down Expand Up @@ -40,13 +40,7 @@ class _OnboardingSecondPageBodyState extends State<OnboardingSecondPageBody> {
key: _heightFormKey,
child: TextFormField(
onChanged: (text) {
if (_heightFormKey.currentState!.validate()) {
_parsedHeight = double.tryParse(text.replaceAll(',', '.'));
checkCorrectInput();
} else {
_parsedHeight = null;
checkCorrectInput();
}
_heightFormKey.currentState!.validate();
},
validator: validateHeight,
decoration: InputDecoration(
Expand Down Expand Up @@ -79,7 +73,7 @@ class _OnboardingSecondPageBodyState extends State<OnboardingSecondPageBody> {
_isUnitSelected[i] = i == index;
}
_heightFormKey.currentState!.validate();
checkCorrectInput();
_weightFormKey.currentState!.validate();
});
},
children: [
Expand All @@ -104,12 +98,7 @@ class _OnboardingSecondPageBodyState extends State<OnboardingSecondPageBody> {
key: _weightFormKey,
child: TextFormField(
onChanged: (text) {
if (_weightFormKey.currentState!.validate()) {
_parsedWeight = double.tryParse(text);
checkCorrectInput();
} else {
checkCorrectInput();
}
_weightFormKey.currentState!.validate();
},
validator: validateWeight,
decoration: InputDecoration(
Expand Down Expand Up @@ -139,7 +128,7 @@ class _OnboardingSecondPageBodyState extends State<OnboardingSecondPageBody> {
_isUnitSelected[i] = i == index;
}
_weightFormKey.currentState!.validate();
checkCorrectInput();
_heightFormKey.currentState!.validate();
});
},
children: [
Expand All @@ -160,54 +149,30 @@ class _OnboardingSecondPageBodyState extends State<OnboardingSecondPageBody> {
}

String? validateHeight(String? value) {
if (value == null) return S.of(context).onboardingWrongHeightLabel;

if (_isImperialSelected) {
// Regex for feet and inches
if (value.isEmpty || !RegExp(r'^[0-9]+([.,][0-9])?$').hasMatch(value)) {
return S.of(context).onboardingWrongHeightLabel;
} else {
return null;
}
} else {
// Regex for cm
if (value.isEmpty || !RegExp(r'^[0-9]+$').hasMatch(value)) {
return S.of(context).onboardingWrongHeightLabel;
} else {
return null;
}
}
return tryParseValidValue(value,
ValueValidator.heightStringValidator(value, S.of(context).onboardingWrongHeightLabel, isImperial: _isImperialSelected),
(String value, bool isImperial) => {
_parsedHeight = ValueValidator.parseHeightInCm(double.tryParse(value), isImperial: _isImperialSelected)
});
}

String? validateWeight(String? value) {
if (value == null) return S.of(context).onboardingWrongWeightLabel;
if (value.isEmpty || !RegExp(r'^[0-9]').hasMatch(value)) {
return S.of(context).onboardingWrongHeightLabel;
} else {
return null;
}
return tryParseValidValue(value,
ValueValidator.weightStringValidator(value, S.of(context).onboardingWrongHeightLabel, isImperial: _isImperialSelected),
(String value, bool isImperial) => {
_parsedWeight = ValueValidator.parseWeightInKg(double.tryParse(value), isImperial: _isImperialSelected)
});
}

/// Check if the input is correct and update the button content
void checkCorrectInput() {
final isHeightValid = _heightFormKey.currentState?.validate() ?? false;
final isWeightValid = _weightFormKey.currentState?.validate() ?? false;

if (isHeightValid && isWeightValid) {
if (_parsedHeight != null && _parsedWeight != null) {
final heightCm = _isImperialSelected
? UnitCalc.feetToCm(_parsedHeight!)
: _parsedHeight!;
final weightKg = _isImperialSelected
? UnitCalc.lbsToKg(_parsedWeight!)
: _parsedWeight!;

widget.setButtonContent(true, heightCm, weightKg, _isImperialSelected);
} else {
widget.setButtonContent(false, null, null, _isImperialSelected);
}
} else {
String? tryParseValidValue(String? value, String? errorLabel, Function(String, bool) parseValue){
if(errorLabel != null) {
widget.setButtonContent(false, null, null, _isImperialSelected);
return errorLabel;
}
parseValue(value!, _isImperialSelected);
_parsedWeight != null && _parsedHeight != null
? widget.setButtonContent(true, _parsedHeight, _parsedWeight, _isImperialSelected)
: widget.setButtonContent(false, null, null, _isImperialSelected);
return null;
}
}
57 changes: 44 additions & 13 deletions lib/features/profile/presentation/widgets/set_weight_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
import 'package:flutter/material.dart';
import 'package:horizontal_picker/horizontal_picker.dart';
import 'package:opennutritracker/core/utils/bounds/ranges_const.dart';
import 'package:opennutritracker/core/utils/bounds/validator.dart';
import 'package:opennutritracker/generated/l10n.dart';

class SetWeightDialog extends StatelessWidget {
static const weightRangeKg = 50.0;
static const weightRangeLbs = 100.0;
static const divisionsPerLbs = 5;
static const divisionsPerKg = 10;
static const kgScrollBuffer = 2;
static const lbsScrollBuffer = 5;

final double userWeight;
final bool usesImperialUnits;
late final int divisions;
late final double maxWeight, minWeight;

const SetWeightDialog(
{super.key, required this.userWeight, required this.usesImperialUnits});

SetWeightDialog(this.userWeight, this.usesImperialUnits, {super.key})
{
super.key;
divisions = calculateDivisions();
}

int calculateDivisions(){
getCloserBound();
return usesImperialUnits
? ((maxWeight - minWeight) * divisionsPerLbs).round()
: ((maxWeight - minWeight) * divisionsPerKg).round();
}

void getCloserBound(){
double initialMaxWeight = usesImperialUnits ? Ranges.maxWeightInLbs : Ranges.maxWeight;
double initialMinWeight = usesImperialUnits ? Ranges.minWeightInLbs : Ranges.minWeight;
initialMinWeight = initialMinWeight - (usesImperialUnits ? lbsScrollBuffer : kgScrollBuffer);
initialMaxWeight = initialMaxWeight + (usesImperialUnits ? lbsScrollBuffer : kgScrollBuffer);
if(initialMaxWeight - userWeight > userWeight - initialMinWeight){
print("sehr leicht");
minWeight = initialMinWeight;
maxWeight = userWeight + (userWeight - minWeight);
}else{
print("sehr schwer");
maxWeight = initialMaxWeight;
minWeight = userWeight - (maxWeight - userWeight);
}
}

@override
Widget build(BuildContext context) {
Expand All @@ -23,14 +56,10 @@ class SetWeightDialog extends StatelessWidget {
HorizontalPicker(
height: 100,
backgroundColor: Colors.transparent,
minValue: usesImperialUnits
? userWeight - weightRangeLbs
: userWeight - weightRangeKg,
maxValue: usesImperialUnits
? userWeight + weightRangeLbs
: userWeight + weightRangeKg,
minValue: minWeight,
maxValue: maxWeight,
initialPosition: InitialPosition.center,
divisions: 1000,
divisions: divisions,
suffix: usesImperialUnits
? S.of(context).lbsLabel
: S.of(context).kgLabel,
Expand All @@ -48,8 +77,10 @@ class SetWeightDialog extends StatelessWidget {
child: Text(S.of(context).dialogCancelLabel)),
TextButton(
onPressed: () {
// TODO validate selected weight
Navigator.pop(context, selectedWeight);
double? weight = ValueValidator.parseWeightInKg(selectedWeight, isImperial: usesImperialUnits);
if(weight != null) Navigator.pop(context, weight);
else ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(S.of(context).onboardingWrongWeightLabel)));
},
child: Text(S.of(context).dialogOKLabel)),
],
Expand Down