https://github.com/codingspecialist/Flutter-BMI
font_awesome_flutter: ^10.7.0
import 'package:flutter/material.dart';
import '../constants.dart';
class BottomButton extends StatelessWidget {
BottomButton({required this.onTap, required this.buttonTitle});
final onTap;
final String buttonTitle;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
child: Center(
child: Text(
buttonTitle,
style: kLargeButtonTextStyle,
)),
color: kBottomContainerColor,
margin: EdgeInsets.only(top: 10.0),
width: double.infinity,
height: kBottomContainerHeight,
),
);
}
}
import 'package:flutter/material.dart';
import '../constants.dart';
class IconContent extends StatelessWidget {
final IconData mIcon;
final String mLabel;
IconContent({required this.mIcon, required this.mLabel});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
mIcon,
size: 80.0,
),
SizedBox(
height: 15.0,
),
Text(
mLabel,
style: kLabelTextStyle,
)
],
);
}
}
import 'package:flutter/material.dart';
class ReusableCard extends StatelessWidget {
final mColor;
final cardChild;
final onPress;
ReusableCard({required this.mColor, this.cardChild, this.onPress});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPress,
child: Container(
child: cardChild,
margin: EdgeInsets.all(15.0),
decoration: BoxDecoration(
color: mColor, borderRadius: BorderRadius.circular(10.0)),
),
);
}
}
import 'package:flutter/material.dart';
class RoundIconButton extends StatelessWidget {
RoundIconButton({required this.icon, required this.onPressed});
final icon;
final onPressed; //dynamic type
@override
Widget build(BuildContext context) {
return RawMaterialButton(
child: Icon(icon),
onPressed: onPressed,
elevation: 0.0,
// 그림자는 버튼이 클릭상태일 때만 발동. 아닐때도 발동하려면 disableElevation 사용
constraints: BoxConstraints.tightFor(
width: 56.0,
height: 56.0,
),
shape: CircleBorder(),
fillColor: Color(0xFF4C4F5E),
);
}
}
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:loginapp/pages/result_page.dart';
import '../calculator_brain.dart';
import '../components/bottom_button.dart';
import '../components/icon_content.dart';
import '../components/reusable_card.dart';
import '../components/round_icon_button.dart';
import '../constants.dart';
enum Gender { male, female }
class InputPage extends StatefulWidget {
@override
_InputPageState createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
Gender? selectedGender;
int height = 180;
int weight = 60;
int age = 20;
@override
Widget build(BuildContext context) {
print('BuildContext : ' + context.hashCode.toString());
return Scaffold(
appBar: AppBar(
title: Center(child: Text('BMI CALCULATOR')),
),
body: Column(
//crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Row(
// Row로 감싸면
children: <Widget>[
Expanded(
// FlatButton은 material Design이 적용되어 있어서 안에 item크기에 맞춰서
// 크기가 줄어들어 버린다. 그래서 GestureDetector를 쓴다.
child: ReusableCard(
onPress: () {
setState(() {
selectedGender = Gender.male;
});
},
// ternary operator (삼항 연산자)
mColor: selectedGender == Gender.male
? kActiveCardColor
: kInactiveCardColor,
cardChild: IconContent(
mIcon: FontAwesomeIcons.mars,
mLabel: 'MALE',
),
),
),
Expanded(
child: ReusableCard(
onPress: () {
setState(() {
selectedGender = Gender.female;
});
},
mColor: selectedGender == Gender.female
? kActiveCardColor
: kInactiveCardColor,
cardChild: IconContent(
mIcon: FontAwesomeIcons.venus,
mLabel: 'FEMALE',
),
),
),
],
)),
Expanded(
child: ReusableCard(
mColor: kActiveCardColor,
cardChild: Column(
// 칼럼에 Expanded면 width 비율이 꽉차지 않는다. Row로 감싸면 해결된다. 혹은 strech를 준다.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'HEIGHT',
style: kLabelTextStyle,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
// 글자 baseline을 맞출 때 함께 줘야하는 속성
children: <Widget>[
Text(
height.toString(),
style: kNumberTextStyle,
),
Text(
'cm',
style: kLabelTextStyle,
)
],
),
SliderTheme(
data: SliderTheme.of(context),
child: Slider(
value: height.toDouble(),
min: 120.0,
max: 220.0,
onChanged: (double newValue) {
setState(() {
height = newValue
.round(); // toInt는 소수점을 짤라내고 round는 반올림 해서 정수로 만들어준다.
});
},
),
),
],
),
),
),
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: ReusableCard(
mColor: kActiveCardColor,
cardChild: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'WEIGHT',
style: kLabelTextStyle,
),
Text(
weight.toString(),
style: kNumberTextStyle,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RoundIconButton(
onPressed: () {
setState(() {
weight--;
});
},
icon: FontAwesomeIcons.minus,
),
SizedBox(
width: 10.0,
),
RoundIconButton(
onPressed: () {
setState(() {
weight++;
});
},
icon: FontAwesomeIcons.plus,
)
],
),
],
),
),
),
Expanded(
child: ReusableCard(
mColor: kActiveCardColor,
cardChild: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'AGE',
style: kLabelTextStyle,
),
Text(
age.toString(),
style: kNumberTextStyle,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RoundIconButton(
onPressed: () {
setState(() {
age--;
});
},
icon: FontAwesomeIcons.minus,
),
SizedBox(
width: 10.0,
),
RoundIconButton(
onPressed: () {
setState(() {
age++;
});
},
icon: FontAwesomeIcons.plus,
)
],
),
],
),
),
),
],
),
),
BottomButton(
buttonTitle: 'CALCULATE',
onTap: () {
// context 에 대해서 참고 (builder를 통해 context를 넘길 수 있다.)
// <https://javaexpert.tistory.com/978>
print('context : ' + context.hashCode.toString());
CalculatorBrain calc = CalculatorBrain(height: height, weight: weight);
print('result ${calc.calculateBMI()}');
print('height ${height}');
print('weight ${weight}');
Navigator.push(context, MaterialPageRoute(builder: (context) {
print('MaterialPageRoute context : ' +
context.hashCode.toString());
return ResultPage(
bmiResult: calc.calculateBMI(),
resultText: calc.getResult(),
interpretation: calc.getInterpretation(),
);
}));
},
)
],
),
);
}
}
import 'package:flutter/material.dart';
import '../components/bottom_button.dart';
import '../components/reusable_card.dart';
import '../constants.dart';
class ResultPage extends StatelessWidget {
final bmiResult;
final resultText;
final interpretation;
ResultPage({required this.bmiResult, required this.resultText, required this.interpretation});
@override
Widget build(BuildContext context) {
print('ResultPage : ' + context.hashCode.toString());
return Scaffold(
appBar: AppBar(
title: Text(
'BMI CALCULATOR',
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.center,
child: Text('Your Result',
style: kTitleTextStyle),
),
),
Expanded(
flex: 5,
child: ReusableCard(
mColor: kActiveCardColor,
cardChild: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
resultText.toUpperCase(),
style: kResultTextStyle,
),
Text(
bmiResult,
style: kBMITextStyle,
),
Text(
interpretation,
style: kBodyTextStyle,
textAlign: TextAlign.center,
),
],
),
),
),
BottomButton(
buttonTitle: 'RE-CALCULATE',
onTap: () {
Navigator.pop(context);
},
),
],
),
);
}
}
import 'dart:math';
class CalculatorBrain {
final int height;
final int weight;
late double _bmi; // private 변수는 언더스코어를 붙인다.
CalculatorBrain({required this.height, required this.weight});
String calculateBMI(){
_bmi = weight / pow(height/100, 2); // 제곱할 때 사용하는 함수
print('bmi : ${_bmi}');
return _bmi.toStringAsFixed(1); // 소수점 이하 개수 정하는 함수
}
String getResult(){
if(_bmi >= 25){
return 'Overweight';
}else if (_bmi > 18.5){
return 'Normal';
}else {
return 'Underweight';
}
}
String getInterpretation(){
if(_bmi >= 25){
return 'You have a higher than normal body weight. Try to exercise more.';
}else if (_bmi > 18.5){
return 'You have a normal body weight. Good job!';
}else {
return 'You have a lower than normal body weight. You can eat a bit more.';
}
}
}
import 'package:flutter/material.dart';
// 디자인 파일 naming convention => k with 낙타표기법
const kBottomContainerHeight = 80.0;
const kActiveCardColor = Color(0xFF1D1E33);
const kInactiveCardColor = Color(0xFF111328);
const kBottomContainerColor = Color(0xFFEB1555);
const kLabelTextStyle = TextStyle(
fontSize: 18.0,
color: Color(0xFF8D8E98),
);
const kNumberTextStyle = TextStyle(
fontSize: 50.0,
fontWeight: FontWeight.w900
);
const kLargeButtonTextStyle = TextStyle(
fontSize: 25.0,
fontWeight: FontWeight.bold,
);
const kTitleTextStyle = TextStyle(
fontSize: 50.0,
fontWeight: FontWeight.bold,
);
const kResultTextStyle = TextStyle(
color: Color(0xFF24D876),
fontSize: 22.0,
fontWeight: FontWeight.bold,
);
const kBMITextStyle = TextStyle(
fontSize: 100.0,
fontWeight: FontWeight.bold,
);
const kBodyTextStyle = TextStyle(
fontSize: 22.0,
);
import 'package:flutter/material.dart';
import 'package:loginapp/pages/input_page.dart';
void main() => runApp(BMICalculator());
class BMICalculator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
sliderTheme: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.white,
inactiveTrackColor: Color(0xFF8D8E98),
thumbColor: Color(0xFFEB1555),
overlayColor: Color(0x1fEB1555),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 15.0),
overlayShape: RoundSliderOverlayShape(overlayRadius: 30.0),
),
primaryColor: Color(0xFF0A0E21),
scaffoldBackgroundColor: Color(0xFF0A0E21),
),
home: InputPage(),
);
}
}