small fix, don't worry about it

This commit is contained in:
Tomas Dvorak
2026-04-10 12:05:40 +02:00
parent 7b7ed0083f
commit 5ab2773f98
55 changed files with 3240 additions and 483 deletions
@@ -0,0 +1,246 @@
import 'package:flutter/material.dart';
enum Gender {
male,
female,
nonBinary,
preferNotToSay;
static Gender fromString(String? value) {
switch (value) {
case 'male':
return Gender.male;
case 'female':
return Gender.female;
case 'non_binary':
return Gender.nonBinary;
case 'prefer_not_to_say':
return Gender.preferNotToSay;
default:
return Gender.preferNotToSay;
}
}
String toDatabaseString() {
switch (this) {
case Gender.male:
return 'male';
case Gender.female:
return 'female';
case Gender.nonBinary:
return 'non_binary';
case Gender.preferNotToSay:
return 'prefer_not_to_say';
}
}
String get displayName {
switch (this) {
case Gender.male:
return 'Male';
case Gender.female:
return 'Female';
case Gender.nonBinary:
return 'Non-binary';
case Gender.preferNotToSay:
return 'Prefer not to say';
}
}
String get emoji {
switch (this) {
case Gender.male:
return '👨';
case Gender.female:
return '👩';
case Gender.nonBinary:
return '🧑';
case Gender.preferNotToSay:
return '👤';
}
}
}
enum HeightUnit {
metric('cm', 'cm'),
imperial('ft/in', 'ft/in');
const HeightUnit(this.code, this.displayName);
final String code;
final String displayName;
}
enum WeightUnit {
metric('kg', 'kg'),
imperial('lbs', 'lbs');
const WeightUnit(this.code, this.displayName);
final String code;
final String displayName;
}
class UnitConversionUtils {
// Height conversions
static double cmToInches(double cm) => cm / 2.54;
static double inchesToCm(double inches) => inches * 2.54;
static String cmToFeetInches(double cm) {
final totalInches = cmToInches(cm);
final feet = (totalInches / 12).floor();
final inches = (totalInches % 12).round();
return "${feet}'${inches}\"";
}
static double feetInchesToCm(String feetInches) {
final parts = feetInches.replaceAll('"', '').replaceAll("'", ' ').split(' ');
final feet = double.tryParse(parts[0]) ?? 0;
final inches = double.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0;
return inchesToCm(feet * 12 + inches);
}
// Weight conversions
static double kgToLbs(double kg) => kg * 2.20462;
static double lbsToKg(double lbs) => lbs / 2.20462;
// BMI calculation
static double calculateBmi(double weightKg, double heightCm) {
if (weightKg <= 0 || heightCm <= 0) return 0;
final heightM = heightCm / 100;
return weightKg / (heightM * heightM);
}
static String getBmiCategory(double bmi) {
if (bmi < 18.5) return 'Underweight';
if (bmi < 25) return 'Normal weight';
if (bmi < 30) return 'Overweight';
return 'Obese';
}
static Color getBmiColor(double bmi) {
if (bmi < 18.5) return Colors.blue;
if (bmi < 25) return Colors.green;
if (bmi < 30) return Colors.orange;
return Colors.red;
}
// Age calculation
static int calculateAge(DateTime birthDate) {
final now = DateTime.now();
int age = now.year - birthDate.year;
if (now.month < birthDate.month ||
(now.month == birthDate.month && now.day < birthDate.day)) {
age--;
}
return age;
}
// Format height for display
static String formatHeight(double cm, HeightUnit unit) {
switch (unit) {
case HeightUnit.metric:
return '${cm.toStringAsFixed(1)} cm';
case HeightUnit.imperial:
return cmToFeetInches(cm);
}
}
// Format weight for display
static String formatWeight(double kg, WeightUnit unit) {
switch (unit) {
case WeightUnit.metric:
return '${kg.toStringAsFixed(1)} kg';
case WeightUnit.imperial:
final lbs = kgToLbs(kg);
return '${lbs.toStringAsFixed(1)} lbs';
}
}
// Parse height from input
static double? parseHeight(String input, HeightUnit unit) {
try {
switch (unit) {
case HeightUnit.metric:
final value = double.tryParse(input.replaceAll(RegExp(r'[^0-9.]'), ''));
return value;
case HeightUnit.imperial:
return feetInchesToCm(input);
}
} catch (e) {
return null;
}
}
// Parse weight from input
static double? parseWeight(String input, WeightUnit unit) {
try {
final value = double.tryParse(input.replaceAll(RegExp(r'[^0-9.]'), ''));
if (value == null) return null;
switch (unit) {
case WeightUnit.metric:
return value;
case WeightUnit.imperial:
return lbsToKg(value);
}
} catch (e) {
return null;
}
}
}
class BiometricData {
final int? age;
final Gender? gender;
final double? heightCm;
final double? weightKg;
final HeightUnit heightUnit;
final WeightUnit weightUnit;
const BiometricData({
this.age,
this.gender,
this.heightCm,
this.weightKg,
this.heightUnit = HeightUnit.metric,
this.weightUnit = WeightUnit.metric,
});
double? get bmi {
if (heightCm == null || weightKg == null) return null;
return UnitConversionUtils.calculateBmi(weightKg!, heightCm!);
}
String get bmiCategory {
final bmiValue = bmi;
if (bmiValue == null) return '';
return UnitConversionUtils.getBmiCategory(bmiValue);
}
String get formattedHeight {
if (heightCm == null) return '';
return UnitConversionUtils.formatHeight(heightCm!, heightUnit);
}
String get formattedWeight {
if (weightKg == null) return '';
return UnitConversionUtils.formatWeight(weightKg!, weightUnit);
}
BiometricData copyWith({
int? age,
Gender? gender,
double? heightCm,
double? weightKg,
HeightUnit? heightUnit,
WeightUnit? weightUnit,
}) {
return BiometricData(
age: age ?? this.age,
gender: gender ?? this.gender,
heightCm: heightCm ?? this.heightCm,
weightKg: weightKg ?? this.weightKg,
heightUnit: heightUnit ?? this.heightUnit,
weightUnit: weightUnit ?? this.weightUnit,
);
}
}
@@ -0,0 +1,180 @@
import 'package:flutter/material.dart';
import '../utils/unit_conversion_utils.dart';
class UnitInputField extends StatefulWidget {
final String labelText;
final IconData prefixIcon;
final String helperText;
final bool enabled;
final ValueChanged<double?> onValueChanged;
final ValueChanged<dynamic>? onUnitChanged;
final double? initialValue;
final bool isHeight;
const UnitInputField({
super.key,
required this.labelText,
required this.prefixIcon,
required this.helperText,
this.enabled = true,
required this.onValueChanged,
this.onUnitChanged,
this.initialValue,
required this.isHeight,
});
@override
State<UnitInputField> createState() => _UnitInputFieldState();
}
class _UnitInputFieldState extends State<UnitInputField> {
late TextEditingController _controller;
late HeightUnit _selectedHeightUnit;
late WeightUnit _selectedWeightUnit;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_selectedHeightUnit = HeightUnit.metric;
_selectedWeightUnit = WeightUnit.metric;
// Set initial value if provided
if (widget.initialValue != null) {
if (widget.isHeight) {
_controller.text = widget.initialValue!.toStringAsFixed(1);
} else {
_controller.text = widget.initialValue!.toStringAsFixed(1);
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onUnitChanged(dynamic unit) {
setState(() {
if (widget.isHeight) {
_selectedHeightUnit = unit as HeightUnit;
} else {
_selectedWeightUnit = unit as WeightUnit;
}
});
// Notify parent widget of unit change
widget.onUnitChanged?.call(unit);
_convertAndNotify();
}
void _showUnitSelector() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Select Unit'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: (widget.isHeight ? HeightUnit.values : WeightUnit.values).map((unit) {
return RadioListTile<dynamic>(
title: Text(widget.isHeight ? (unit as HeightUnit).displayName : (unit as WeightUnit).displayName),
value: unit,
groupValue: widget.isHeight ? _selectedHeightUnit : _selectedWeightUnit,
onChanged: widget.enabled ? (value) {
_onUnitChanged(value);
Navigator.of(context).pop();
} : null,
);
}).toList(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
],
);
},
);
}
void _onTextChanged(String text) {
_convertAndNotify();
}
void _convertAndNotify() {
final text = _controller.text.trim();
if (text.isEmpty) {
widget.onValueChanged(null);
return;
}
double? valueInCmOrKg;
if (widget.isHeight) {
valueInCmOrKg = UnitConversionUtils.parseHeight(text, _selectedHeightUnit);
} else {
valueInCmOrKg = UnitConversionUtils.parseWeight(text, _selectedWeightUnit);
}
widget.onValueChanged(valueInCmOrKg);
}
String get _unitDisplayText {
if (widget.isHeight) {
return _selectedHeightUnit.displayName;
} else {
return _selectedWeightUnit.displayName;
}
}
@override
Widget build(BuildContext context) {
return Row(
children: [
// Input field
Expanded(
flex: 3,
child: TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: widget.labelText,
prefixIcon: Icon(widget.prefixIcon),
helperText: widget.helperText,
suffixText: _unitDisplayText,
isDense: true, // Make the input field more compact
),
enabled: widget.enabled,
onChanged: widget.enabled ? _onTextChanged : null,
),
),
const SizedBox(width: 8),
// Unit selector - custom button
Container(
width: 45,
height: 32,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(4),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(4),
onTap: widget.enabled ? () => _showUnitSelector() : null,
child: Center(
child: Text(
_unitDisplayText,
style: const TextStyle(fontSize: 12),
),
),
),
),
),
],
);
}
}