diff --git a/README.md b/README.md index f2129e3b..4eb2d518 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,45 @@ -![App Brewery Banner](https://github.com/londonappbrewery/Images/blob/master/AppBreweryBanner.png) +This project is part of a series of projects to be completed by students of the [AppBrewery](https://www.appbrewery.co/p/flutter-development-bootcamp-with-dart) flutter course. -# BMI Calculator ๐Ÿ’ช -## Our Goal +## ๐Ÿงฎ The Project Brief -The objective of this tutorial is to look at how we can customise Flutter Widgets to achieve our own beautiful user interface designs. If you have a designer on board, no matter how unconventional their designs are, we can create them using Flutter. +To make a Body Mass Index Calculator inspired by the beautiful designs made by [Ruben Vaalt](https://dribbble.com/shots/4585382-Simple-BMI-Calculator). It will be a multi screen app with simple functionality but full-on custom styling. +![Finished App](https://github.com/londonappbrewery/Images/blob/master/bmi-calc-demo.gif) +## ๐ŸŽฏ Features -## What you will create +* You can switch between English and Spanish by touching the toggle button -Weโ€™re going to make a Body Mass Index Calculator inspired by the beautiful designs made by [Ruben Vaalt](https://dribbble.com/shots/4585382-Simple-BMI-Calculator). It will be a multi screen app with simple functionality but full-on custom styling. +- Add gender, age, weight and height +- Get your BMI result with a custom description. -![Finished App](https://github.com/londonappbrewery/Images/blob/master/bmi-calc-demo.gif) +## ๐Ÿงฌ Technologies & Languages Used + +- Dart +- Flutter +- Github + + +## ๐Ÿ›Ž๏ธ Contributions, Issues & Forking + +If you have any issues setting up the project or you come across any unintended bugs or problems, please do submit an issue to the [BMI Calculator](https://github.com/Psiale/bmi-calculator-flutter/issues) page. + +If you want to make your own changes, modifications or improvements to our project, go ahead and Fork it! +1. [Fork it](https://github.com/Psiale/bmi-calculator-flutter/fork) + +2. Create your working branch (git checkout -b [choose-a-name]) + +3. Commit your changes (git commit -m 'what this commit will fix/add/improve') +4. Push to the branch (git push origin [chosen-name]) +5. Create a new Pull Request -## What you will learn +## ๐ŸคŸ๐Ÿฝ๐Ÿ˜„ ๐Ÿ˜›๐Ÿค™๐Ÿพ Creator -- How to use Flutter themes to create coherent branding. -- How to create multi-page apps using Flutter Routes and Navigator. -- How to extract and refactor Flutter Widgets with a click of the button. -- How to pass functions as parameters and fields. -- How to use the GestureDetector Widget to detect more than just a tap. -- How to use custom colour palettes by using hex codes. -- How to customise Flutter Widgets to achieve a specific design style. -- Understand Dart Enums and the Ternary Operator. -- Learn about composition vs. inheritance and the Flutter way of creating custom UI. -- Understand the difference between const and final in Dart and when to use each. +Alexis Sanchez +- [Github](https://github.com/Psiale) +- [Linkedin](https://www.linkedin.com/in/alexis-sanchez-dev/) ->This is a companion project to The App Brewery's Complete Flutter Development Bootcamp, check out the full course at [www.appbrewery.co](https://www.appbrewery.co/) +## ๐Ÿ™Œ๐Ÿพ Show Your Support -![End Banner](https://github.com/londonappbrewery/Images/blob/master/readme-end-banner.png) +Give a โญ๏ธ if you like this project! diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 00000000..4738b94a --- /dev/null +++ b/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=C:\Users\front\Documents\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Users\front\Desktop\Flutter_projects\bmi-calculator-flutter" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build\ios" +export "OTHER_LDFLAGS=$(inherited) -framework Flutter" +export "FLUTTER_FRAMEWORK_DIR=C:\Users\front\Documents\flutter\bin\cache\artifacts\engine\ios" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=false" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.packages" diff --git a/lib/constants.dart b/lib/constants.dart new file mode 100644 index 00000000..fb25ed37 --- /dev/null +++ b/lib/constants.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +const bottomContainerHeight = 80.0; +const inactiveCardColor = Color(0xFF1D1E33); +const activeCardColor = Color(0xFF111328); +const bottomContainerColor = Color(0xFFEB1555); + +const LabelTextStyle = TextStyle(fontSize: 18.0, color: Color(0xFF8D8398)); + +const NumberTextStyle = TextStyle(fontSize: 50.0, fontWeight: FontWeight.w900); + +const LargeButtonStyle = TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold); + +const TitleTextStyle = TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold); + +const resultTextStyle = TextStyle( + color: Color(0xFF24D876), fontSize: 22.0, fontWeight: FontWeight.bold); + +const BMITextStyle = TextStyle(fontSize: 100.0, fontWeight: FontWeight.bold); + +const BMIDescriptionTextStyle = TextStyle(fontSize: 22.0,); diff --git a/lib/logic/bmiCalculator.dart b/lib/logic/bmiCalculator.dart new file mode 100644 index 00000000..83ec0d44 --- /dev/null +++ b/lib/logic/bmiCalculator.dart @@ -0,0 +1,33 @@ +import 'dart:math'; + +class BMICalculator { + BMICalculator({this.weight, this.height}); + double weight; + double height; + double _bmi; + + calculate() { + _bmi = weight / pow(height / 100, 2); + return _bmi.toStringAsFixed(1); + } + + String getBMILabel() { + if (_bmi >= 25) { + return 'Overweight'; + } else if (_bmi > 18.5) { + return 'Normal'; + } else { + return 'UnderWeight'; + } + } + + String getBMIDescription() { + if (_bmi >= 25) { + return 'You have a Higher than normal body weight. Try to excercise 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'; + } + } +} diff --git a/lib/logic/bmiResults.dart b/lib/logic/bmiResults.dart new file mode 100644 index 00000000..808e3841 --- /dev/null +++ b/lib/logic/bmiResults.dart @@ -0,0 +1,7 @@ +class BMIResults { + BMIResults({this.bmi, this.label, this.description}); + + String bmi; + String label; + String description; +} diff --git a/lib/main.dart b/lib/main.dart index 78f51b11..9e680a37 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,7 @@ +import 'package:bmi_calculator/pages/ResultsPage.dart'; import 'package:flutter/material.dart'; +import './pages/InputPage.dart'; +import './pages/ResultsPage.dart'; void main() => runApp(BMICalculator()); @@ -6,29 +9,25 @@ class BMICalculator extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: InputPage(), - ); - } -} - -class InputPage extends StatefulWidget { - @override - _InputPageState createState() => _InputPageState(); -} - -class _InputPageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('BMI CALCULATOR'), - ), - body: Center( - child: Text('Body Text'), - ), - floatingActionButton: FloatingActionButton( - child: Icon(Icons.add), - ), + initialRoute: '/', + routes: { + '/': (context) => InputPage(), + '/results': (context) => ResultsPage(), + }, + debugShowCheckedModeBanner: false, + theme: ThemeData.dark().copyWith( + primaryColor: Color(0xFF0A0E21), + scaffoldBackgroundColor: Color(0xFF0A0E21), + sliderTheme: SliderThemeData( + overlayShape: RoundSliderOverlayShape(overlayRadius: 30.0), + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 15.0), + inactiveTickMarkColor: Colors.pink[600], + overlayColor: Color(0x29EB1555), + valueIndicatorColor: Colors.pinkAccent, + inactiveTrackColor: Colors.grey[600], + activeTrackColor: Colors.pinkAccent, + thumbColor: Colors.pink[600]), + indicatorColor: Colors.pinkAccent), ); } } diff --git a/lib/pages/InputPage.dart b/lib/pages/InputPage.dart new file mode 100644 index 00000000..7898d504 --- /dev/null +++ b/lib/pages/InputPage.dart @@ -0,0 +1,227 @@ +import 'package:bmi_calculator/pages/ResultsPage.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:flutter/material.dart'; +import '../widgetBuilder/darkContainer.dart'; +import 'package:bmi_calculator/widgetBuilder/fontAwesomeWidget.dart'; +import '../widgetBuilder/slider.dart'; +import '../constants.dart'; +import '../widgetBuilder/threelevelsWidget.dart'; +import '../widgetBuilder/countWidget.dart'; +import '../widgetBuilder/bottomButton.dart'; +import '../logic/bmiCalculator.dart'; + +BMICalculator bmiCalculator = + BMICalculator(height: _heightCurrentValue, weight: _weightCurrentValue); + +double _heightCurrentValue = 184.0; +double _weightCurrentValue = 75.0; +double _ageCurrentValue = 22.0; +enum Gender { male, female } +Gender selectedGender; + +class InputPage extends StatefulWidget { + @override + _InputPageState createState() => _InputPageState(); +} + +class _InputPageState extends State { + Color maleCardColor = activeCardColor; + Color femaleCardColor = activeCardColor; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('BMI CALCULATOR'), + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: new DarkContainer( + onPress: () { + setState(() { + selectedGender = Gender.male; + }); + }, + color: selectedGender == Gender.male + ? inactiveCardColor + : activeCardColor, + cardChild: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FontAwesomeWidget( + margin: EdgeInsets.only(bottom: 8.0), + icon: FontAwesomeIcons.mars, + size: 60.0, + textColor: Color(0xFF8D8E98), + textSize: 18.0, + iconText: 'MALE', + ), + ], + ), + )), + Expanded( + child: new DarkContainer( + onPress: () { + setState(() { + selectedGender = Gender.female; + }); + }, + color: selectedGender == Gender.female + ? inactiveCardColor + : activeCardColor, + cardChild: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FontAwesomeWidget( + margin: EdgeInsets.only(bottom: 8.0), + icon: FontAwesomeIcons.venus, + size: 60.0, + iconText: 'FEMALE', + textColor: Color(0xFF8D8E98), + textSize: 18.0, + ), + ], + ), + )) + ], + ), + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: new DarkContainer( + cardChild: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'HEIGHT', + style: LabelTextStyle, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text(_heightCurrentValue.round().toString(), + style: NumberTextStyle), + Text( + 'cm', + style: LabelTextStyle, + ), + ], + ), + new CustomSlider( + currentValue: _heightCurrentValue, + minValue: 120.0, + maxValue: 220.0, + onPress: (double value) { + setState(() { + _heightCurrentValue = value; + bmiCalculator.height = _heightCurrentValue; + print(bmiCalculator.height); + }); + }, + ) + ]), + color: inactiveCardColor, + ), + ) + // change the DarkContainer width and height to have percentage sizes + // Solve this instead of using fixed sizes I had to use Expanded widgets + ], + ), + ), + Row( + children: [ + Expanded( + child: new DarkContainer( + color: inactiveCardColor, + cardChild: new ThreeLevelWidget( + // had to end up building the countWidget + labelText: 'WEIGHT', + numberText: _weightCurrentValue.round().toString(), + customChild: new CountWidget(firstCallBack: () { + if (_weightCurrentValue < 200) { + print(_weightCurrentValue); + setState(() { + _weightCurrentValue++; + bmiCalculator.weight = _weightCurrentValue; + print(bmiCalculator.weight); + }); + } + }, secondCallBack: () { + if (_weightCurrentValue > 0) { + setState(() { + _weightCurrentValue--; + bmiCalculator.weight = _weightCurrentValue; + print(bmiCalculator.weight); + }); + } + })), + )), + Expanded( + child: new DarkContainer( + color: inactiveCardColor, + cardChild: new ThreeLevelWidget( + labelText: 'AGE', + numberText: _ageCurrentValue.round().toString(), + customChild: new CountWidget(firstCallBack: () { + if (_ageCurrentValue < 100) { + setState(() { + _ageCurrentValue++; + }); + } + }, secondCallBack: () { + if (_ageCurrentValue > 0) { + setState(() { + _ageCurrentValue--; + }); + } + }), + ), + )) + ], + ), + Container( + child: BottomButton( + title: "CALCULATE", + onClick: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ResultsPage( + weight: bmiCalculator.calculate(), + weightLabel: bmiCalculator.getBMILabel(), + weightDescription: + bmiCalculator.getBMIDescription(), + ))); + }, + ), + width: double.infinity, + height: bottomContainerHeight, + margin: EdgeInsets.only(top: 5.0), + decoration: BoxDecoration( + color: bottomContainerColor, + )), + ], + ), + ), + ); + } + + // selectedGender(Gender gender) { + // if (gender == Gender.male) { + // maleCardColor = activeCardColor; + // femaleCardColor = inactiveCardColor; + // } else { + // femaleCardColor = activeCardColor; + // maleCardColor = inactiveCardColor; + // } + // } +} diff --git a/lib/pages/ResultsPage.dart b/lib/pages/ResultsPage.dart new file mode 100644 index 00000000..e5a1f89a --- /dev/null +++ b/lib/pages/ResultsPage.dart @@ -0,0 +1,72 @@ +import 'package:bmi_calculator/constants.dart'; +import 'package:flutter/material.dart'; +import '../widgetBuilder/darkContainer.dart'; +import '../widgetBuilder/bottomButton.dart'; + +class ResultsPage extends StatelessWidget { + ResultsPage({ + this.weightLabel, + this.weight, + this.weightDescription, + }); + + final String weightLabel; + final String weight; + final String weightDescription; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Results"), + ), + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: new DarkContainer( + cardChild: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + (weightLabel != null) + ? weightLabel + : "No weight provided", + style: resultTextStyle, + ), + Text( + (weight != null) ? weight : "N/A", + style: BMITextStyle, + ), + Text( + (weightDescription != null) + ? weightDescription + : "No weight description", + textAlign: TextAlign.center, + style: BMIDescriptionTextStyle, + ) + ], + ), + color: activeCardColor, + ), + ), + Container( + child: BottomButton( + title: "RE-CALCULATE", + onClick: () { + Navigator.pop(context); + }), + width: double.infinity, + height: bottomContainerHeight, + margin: EdgeInsets.only(top: 5.0), + decoration: BoxDecoration( + color: bottomContainerColor, + )) + ], + ), + ), + ); + } +} diff --git a/lib/widgetBuilder/bottomButton.dart b/lib/widgetBuilder/bottomButton.dart new file mode 100644 index 00000000..caf43a5f --- /dev/null +++ b/lib/widgetBuilder/bottomButton.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class BottomButton extends StatelessWidget { + const BottomButton({ + @required this.title, + this.onClick, + //add a route parameter and a checker with a default value for it. + Key key, + }) : super(key: key); + + final String title; + final Function onClick; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onClick, + child: Center( + child: Text( + title, + style: LargeButtonStyle, + ))); + } +} diff --git a/lib/widgetBuilder/countWidget.dart b/lib/widgetBuilder/countWidget.dart new file mode 100644 index 00000000..2e80db38 --- /dev/null +++ b/lib/widgetBuilder/countWidget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class CountWidget extends StatelessWidget { + CountWidget({@required this.firstCallBack, @required this.secondCallBack}); + final Function firstCallBack; + final Function secondCallBack; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RoundIconButton(callBack: firstCallBack, icon: Icons.add), + SizedBox( + width: 20.0, + ), + RoundIconButton(callBack: secondCallBack, icon: Icons.remove) + ], + ); + } +} + +class RoundIconButton extends StatelessWidget { + RoundIconButton({@required this.callBack, this.icon}); + + final Function callBack; + final IconData icon; + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + child: Icon(icon), + onPressed: callBack, + elevation: 6.0, + constraints: BoxConstraints.tightFor( + width: 56.0, + height: 56.0, + ), + shape: CircleBorder(), + fillColor: Color(0xFF4C4F5E), + ); + } +} diff --git a/lib/widgetBuilder/darkContainer.dart b/lib/widgetBuilder/darkContainer.dart new file mode 100644 index 00000000..b21b55d3 --- /dev/null +++ b/lib/widgetBuilder/darkContainer.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class DarkContainer extends StatelessWidget { + DarkContainer({@required this.color, this.cardChild, this.onPress}); + final Color color; + final cardChild; + final Function onPress; + + @override + Widget build(BuildContext context) { + double height = MediaQuery.of(context).size.height; + double width = MediaQuery.of(context).size.width; + return GestureDetector( + onTap: onPress, + child: Container( + child: cardChild, + margin: EdgeInsets.all(15.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: color, + ), + ), + ); + } +} diff --git a/lib/widgetBuilder/fontAwesomeWidget.dart b/lib/widgetBuilder/fontAwesomeWidget.dart new file mode 100644 index 00000000..060afbf9 --- /dev/null +++ b/lib/widgetBuilder/fontAwesomeWidget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class FontAwesomeWidget extends StatelessWidget { + FontAwesomeWidget( + {@required this.icon, + @required this.size, + this.color, + @required this.iconText, + this.callback, + this.margin, + @required this.textColor, + @required this.textSize}); + + final icon; + final double size; + final Color color; + final callback; + final EdgeInsetsGeometry margin; + final String iconText; + final double textSize; + final Color textColor; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: margin, + child: Icon( + // Use the FaIcon Widget + FontAwesomeIcons class for the IconData + icon, + size: size, + color: color, + )), + Text( + iconText, + style: TextStyle(color: textColor, fontSize: textSize), + ) + ], + ); + } +} diff --git a/lib/widgetBuilder/slider.dart b/lib/widgetBuilder/slider.dart new file mode 100644 index 00000000..a91ea8aa --- /dev/null +++ b/lib/widgetBuilder/slider.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class CustomSlider extends StatelessWidget { + CustomSlider( + {this.currentValue, + this.values, + this.minValue, + this.maxValue, + this.numDivisions, + this.onPress}); + final List values; + final double currentValue; + final double minValue; + final double maxValue; + final int numDivisions; + final Function onPress; + + @override + Widget build(BuildContext context) { + return Slider( + value: currentValue, + onChanged: onPress, + min: minValue, + max: maxValue, + label: currentValue.round().toString(), + divisions: numDivisions, + ); + // implement this + } +} diff --git a/lib/widgetBuilder/threelevelsWidget.dart b/lib/widgetBuilder/threelevelsWidget.dart new file mode 100644 index 00000000..bd320aee --- /dev/null +++ b/lib/widgetBuilder/threelevelsWidget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class ThreeLevelWidget extends StatelessWidget { + ThreeLevelWidget({ + @required this.labelText, + @required this.numberText, + @required this.customChild, + }); + final String labelText; + final String numberText; + final customChild; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + labelText, + style: LabelTextStyle, + ), + Text( + numberText, + style: NumberTextStyle, + ), + customChild + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 6dc027c6..cf48ab8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter cupertino_icons: ^0.1.2 - + font_awesome_flutter: '>= 4.7.0' dev_dependencies: flutter_test: sdk: flutter