1
0
mirror of https://github.com/nisrulz/flutter-examples.git synced 2025-11-08 20:50:04 +00:00

BMI calculator example (#130)

This commit is contained in:
lutaii
2023-10-12 18:08:26 +02:00
committed by GitHub
parent f761e553e4
commit 36c8bed38c
19 changed files with 780 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
import 'dart:math';
import 'package:bmi_calculator/body_model.dart';
// I've used weight/height^2 (kg/m^2)
// You can expand this logic
double calculateBMI({required BodyModel bodyModel}) {
return (bodyModel.weight) / pow(bodyModel.height / 100, 2);
}

View File

@@ -0,0 +1,19 @@
enum Sex {
male,
female,
}
// User body model class
class BodyModel {
Sex sex;
int height;
int weight;
int age;
BodyModel({
required this.sex,
required this.height,
required this.weight,
required this.age,
});
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import '../palette.dart';
class CalculateButton extends StatelessWidget {
const CalculateButton({
super.key,
required this.onTap,
});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height / 12,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Palette.action,
),
child: TextButton(
style: ButtonStyle(
overlayColor:
MaterialStateProperty.all(Colors.white.withOpacity(0.10)),
),
onPressed: onTap,
child: const Text(
'CALCULATE YOUR BMI',
style: TextStyle(
color: Palette.textActive,
fontSize: 20,
),
),
),
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:bmi_calculator/calculator/calculate_button.dart';
import 'package:flutter/material.dart';
import 'package:bmi_calculator/calculator/height_widget.dart';
import 'package:bmi_calculator/calculator/int_picker_widget.dart';
import 'package:bmi_calculator/calculator/sex_widget.dart';
import 'package:bmi_calculator/result/result_page.dart';
import 'package:bmi_calculator/body_model.dart';
import 'package:bmi_calculator/palette.dart';
class CalculatorBody extends StatefulWidget {
const CalculatorBody({
super.key,
required this.model,
});
final BodyModel model;
@override
State<CalculatorBody> createState() => _CalculatorBodyState();
}
class _CalculatorBodyState extends State<CalculatorBody> {
@override
Widget build(BuildContext context) {
return Container(
color: Palette.background,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SexWidget(
sex: widget.model.sex,
onMaleTap: () => setState(() {
// Set sex to male
widget.model.sex = Sex.male;
}),
onFemaleTap: () => setState(() {
// Set sex to female
widget.model.sex = Sex.female;
})),
HeightWidget(
height: widget.model.height,
onHeightChanged: (height) => setState(() {
// Set height and round to Int
widget.model.height = height.toInt();
}),
),
SizedBox(
height: (MediaQuery.of(context).size.width - 48) / 2,
child: Row(
children: [
Expanded(
// Weight widget
child: IntPickerWidget(
title: 'Weight',
onIncreaseTap: () => setState(() {
// Increase weight
widget.model.weight++;
}),
onDecreaseTap: () => setState(() {
// Decrease weight
widget.model.weight--;
}),
value: widget.model.weight,
),
),
const SizedBox(
width: 5,
),
Expanded(
// Age widget
child: IntPickerWidget(
title: 'Age',
onIncreaseTap: () => setState(() {
// Increase age
widget.model.age++;
}),
onDecreaseTap: () => setState(() {
// Decrease age
widget.model.age--;
}),
value: widget.model.age,
),
)
],
),
),
CalculateButton(
onTap: () {
// Navigate to Result Page
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => ResultPage(model: widget.model)),
),
);
},
),
],
),
),
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:bmi_calculator/calculator/calculator_body.dart';
import 'package:flutter/material.dart';
import 'package:bmi_calculator/body_model.dart';
import 'package:bmi_calculator/palette.dart';
class CalculatorPage extends StatelessWidget {
CalculatorPage({
Key? key,
required this.title,
}) : super(key: key);
final String title;
final BodyModel model = BodyModel(
sex: Sex.male,
height: 183,
weight: 74,
age: 19,
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Palette.background,
),
body: CalculatorBody(model: model),
);
}
}

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import '../palette.dart';
class HeightWidget extends StatelessWidget {
const HeightWidget({
super.key,
required this.height,
required this.onHeightChanged,
});
final int height;
final Function(double) onHeightChanged;
@override
Widget build(BuildContext context) {
return Container(
height: (MediaQuery.of(context).size.width - 48) / 2,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Palette.cardBackgroundInactive,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'HEIGHT',
style: TextStyle(
fontSize: 23,
fontWeight: FontWeight.w600,
color: Palette.textInactive,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
height.round().toString(),
style: const TextStyle(
fontSize: 50,
fontWeight: FontWeight.w800,
color: Palette.textActive,
),
),
const Text(
'cm',
style: TextStyle(
fontSize: 30,
color: Palette.textInactive,
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 1.0,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 15,
),
),
child: Slider(
value: height.toDouble(),
min: 150.0,
max: 210.0,
activeColor: Palette.textActive,
inactiveColor: Palette.textInactive,
thumbColor: Palette.action,
onChanged: onHeightChanged,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,84 @@
import 'package:bmi_calculator/palette.dart';
import 'package:flutter/material.dart';
class IntPickerWidget extends StatelessWidget {
const IntPickerWidget({
super.key,
required this.title,
required this.onIncreaseTap,
required this.onDecreaseTap,
required this.value,
});
final String title;
final VoidCallback onIncreaseTap;
final VoidCallback onDecreaseTap;
final int value;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Palette.cardBackgroundInactive,
),
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title.toUpperCase(),
style: const TextStyle(
fontSize: 23,
fontWeight: FontWeight.w600,
color: Palette.textInactive,
),
),
Text(
value.toString(),
style: const TextStyle(
fontSize: 50,
fontWeight: FontWeight.w800,
color: Palette.textActive,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
onTap: onDecreaseTap,
child: Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Palette.cardBackgroundActive,
),
child: const Icon(
Icons.remove,
color: Palette.textActive,
),
),
),
InkWell(
onTap: onIncreaseTap,
child: Container(
width: 56,
height: 56,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Palette.cardBackgroundActive,
),
child: const Icon(
Icons.add,
color: Palette.textActive,
),
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,103 @@
import 'package:bmi_calculator/palette.dart';
import 'package:flutter/material.dart';
import '../body_model.dart';
class SexWidget extends StatelessWidget {
const SexWidget({
super.key,
required this.sex,
required this.onMaleTap,
required this.onFemaleTap,
});
final Sex sex;
final VoidCallback onMaleTap;
final VoidCallback onFemaleTap;
@override
Widget build(BuildContext context) {
return SizedBox(
height: (MediaQuery.of(context).size.width - 48) / 2,
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: onMaleTap,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: sex == Sex.male
? Palette.cardBackgroundActive
: Palette.cardBackgroundInactive,
),
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.male_rounded,
size: 100,
color: sex == Sex.male
? Palette.textActive
: Palette.textInactive,
),
Text(
'MALE',
style: TextStyle(
fontSize: 23,
fontWeight: FontWeight.w600,
color: sex == Sex.male
? Palette.textActive
: Palette.textInactive,
),
),
],
),
),
),
),
const SizedBox(
width: 5,
),
Expanded(
child: GestureDetector(
onTap: onFemaleTap,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: sex == Sex.female
? Palette.cardBackgroundActive
: Palette.cardBackgroundInactive,
),
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.female_rounded,
size: 100,
color: sex == Sex.female
? Palette.textActive
: Palette.textInactive,
),
Text(
'FEMALE',
style: TextStyle(
fontSize: 23,
fontWeight: FontWeight.w600,
color: sex == Sex.female
? Palette.textActive
: Palette.textInactive,
),
),
],
),
),
),
)
],
),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:bmi_calculator/calculator/calculator_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Prevent landscape orientation
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BMI Calculator',
home: CalculatorPage(title: 'BMI CALCULATOR'),
);
}
}

View File

@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
// Color constants
class Palette {
static const background = Color(0xFF0A0D22);
static const cardBackgroundInactive = Color(0xFF121428);
static const cardBackgroundActive = Color(0xFF1D1F33);
static const textInactive = Color(0xFF8D8E99);
static const textActive = Color(0xFFFFFFFF);
static const action = Color(0xFFEC1554);
static const normalResult = Color(0xFF4BE57A);
static const underweightResult = Color(0xFFE5D94B);
static const overWeightResult = Color(0xFFE54B4B);
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import '../palette.dart';
import 'result_content_widget.dart';
class ResultBodyWidget extends StatelessWidget {
const ResultBodyWidget({
super.key,
required this.result,
});
final double result;
@override
Widget build(BuildContext context) {
return Container(
color: Palette.background,
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(72.0),
child: ResultContentWidget(result: result),
),
);
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import '../palette.dart';
class ResultContentWidget extends StatelessWidget {
const ResultContentWidget({
super.key,
required this.result,
});
final double result;
Color resultColor() {
if (result < 18.5) {
return Palette.underweightResult;
} else if (result > 25) {
return Palette.overWeightResult;
} else {
return Palette.normalResult;
}
}
String diagnosis() {
if (result < 18.5) {
return 'UNDERWEIGHT';
} else if (result > 25) {
return 'OVERWEIGHT';
} else {
return 'NORMAL';
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
diagnosis(),
style: TextStyle(
color: resultColor(),
fontWeight: FontWeight.w600,
fontSize: 25,
),
),
Text(
// [toStringAsFixed] for decimal-point string-representation
result.toStringAsFixed(1),
style: const TextStyle(
color: Palette.textActive,
fontWeight: FontWeight.w600,
fontSize: 100,
),
),
const Column(
children: [
Text(
'Normal BMI range:',
style: TextStyle(
color: Palette.textInactive,
fontWeight: FontWeight.w500,
fontSize: 20,
),
),
SizedBox(height: 8),
Text(
'18.5 - 25 kg/m2',
style: TextStyle(
color: Palette.textActive,
fontWeight: FontWeight.w500,
fontSize: 20,
),
),
],
),
],
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:bmi_calculator/body_model.dart';
import 'package:bmi_calculator/palette.dart';
import 'package:bmi_calculator/result/result_body_widget.dart';
import 'package:flutter/material.dart';
import '../bmi_calculator.dart';
class ResultPage extends StatelessWidget {
final BodyModel model;
const ResultPage({
super.key,
required this.model,
});
@override
Widget build(BuildContext context) {
final result = calculateBMI(bodyModel: model);
return Scaffold(
appBar: AppBar(
title: const Text('RESULT'),
backgroundColor: Palette.background,
leading: IconButton(
onPressed: () {
// Navigate back to previous page
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back_ios),
),
),
body: ResultBodyWidget(result: result),
);
}
}