Last active
April 14, 2020 05:26
-
-
Save MarcinusX/2cbef7d9c16acee7007ea72872107433 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
https://marcinszalek.pl/tag/bmi-calculator/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:bmi_calculator/card_title.dart'; | |
import 'package:bmi_calculator/height/height_picker.dart'; | |
import 'package:bmi_calculator/widget_utils.dart'; | |
import 'package:flutter/material.dart'; | |
class HeightCard extends StatefulWidget { | |
final int height; | |
const HeightCard({Key key, this.height}) : super(key: key); | |
@override | |
HeightCardState createState() => HeightCardState(); | |
} | |
class HeightCardState extends State<HeightCard> { | |
int height; | |
@override | |
void initState() { | |
super.initState(); | |
height = widget.height ?? 170; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Card( | |
child: Padding( | |
padding: EdgeInsets.only(top: screenAwareSize(16.0, context)), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
CardTitle("HEIGHT", subtitle: "(cm)"), | |
Expanded( | |
child: Padding( | |
padding: EdgeInsets.only(bottom: screenAwareSize(8.0, context)), | |
child: LayoutBuilder(builder: (context, constraints) { | |
return HeightPicker( | |
widgetHeight: constraints.maxHeight, | |
height: height, | |
onChange: (val) => setState(() => height = val), | |
); | |
}), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math' as math; | |
import 'package:bmi_calculator/height/height_slider.dart'; | |
import 'package:bmi_calculator/height/height_styles.dart'; | |
import 'package:bmi_calculator/widget_utils.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_svg/flutter_svg.dart'; | |
class HeightPicker extends StatefulWidget { | |
final int maxHeight; | |
final int minHeight; | |
final int height; | |
final double widgetHeight; | |
final ValueChanged<int> onChange; | |
const HeightPicker( | |
{Key key, | |
this.height, | |
this.widgetHeight, | |
this.onChange, | |
this.maxHeight = 190, | |
this.minHeight = 145}) | |
: super(key: key); | |
int get totalUnits => maxHeight - minHeight; | |
@override | |
_HeightPickerState createState() => _HeightPickerState(); | |
} | |
class _HeightPickerState extends State<HeightPicker> { | |
double startDragYOffset; | |
int startDragHeight; | |
double get _pixelsPerUnit { | |
return _drawingHeight / widget.totalUnits; | |
} | |
double get _sliderPosition { | |
double halfOfBottomLabel = labelsFontSize / 2; | |
int unitsFromBottom = widget.height - widget.minHeight; | |
return halfOfBottomLabel + unitsFromBottom * _pixelsPerUnit; | |
} | |
///returns actual height of slider to be able to slide | |
double get _drawingHeight { | |
double totalHeight = widget.widgetHeight; | |
double marginBottom = marginBottomAdapted(context); | |
double marginTop = marginTopAdapted(context); | |
return totalHeight - (marginBottom + marginTop + labelsFontSize); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
behavior: HitTestBehavior.translucent, | |
onTapDown: _onTapDown, | |
onVerticalDragStart: _onDragStart, | |
onVerticalDragUpdate: _onDragUpdate, | |
child: Stack( | |
children: <Widget>[ | |
_drawPersonImage(), | |
_drawSlider(), | |
_drawLabels(), | |
], | |
), | |
); | |
} | |
_onTapDown(TapDownDetails tapDownDetails) { | |
int height = _globalOffsetToHeight(tapDownDetails.globalPosition); | |
widget.onChange(_normalizeHeight(height)); | |
} | |
int _normalizeHeight(int height) { | |
return math.max(widget.minHeight, math.min(widget.maxHeight, height)); | |
} | |
int _globalOffsetToHeight(Offset globalOffset) { | |
RenderBox getBox = context.findRenderObject(); | |
Offset localPosition = getBox.globalToLocal(globalOffset); | |
double dy = localPosition.dy; | |
dy = dy - marginTopAdapted(context) - labelsFontSize / 2; | |
int height = widget.maxHeight - (dy ~/ _pixelsPerUnit); | |
return height; | |
} | |
_onDragStart(DragStartDetails dragStartDetails) { | |
int newHeight = _globalOffsetToHeight(dragStartDetails.globalPosition); | |
widget.onChange(newHeight); | |
setState(() { | |
startDragYOffset = dragStartDetails.globalPosition.dy; | |
startDragHeight = newHeight; | |
}); | |
} | |
_onDragUpdate(DragUpdateDetails dragUpdateDetails) { | |
double currentYOffset = dragUpdateDetails.globalPosition.dy; | |
double verticalDifference = startDragYOffset - currentYOffset; | |
int diffHeight = verticalDifference ~/ _pixelsPerUnit; | |
int height = _normalizeHeight(startDragHeight + diffHeight); | |
setState(() => widget.onChange(height)); | |
} | |
Widget _drawSlider() { | |
return Positioned( | |
child: HeightSlider(height: widget.height), | |
left: 0.0, | |
right: 0.0, | |
bottom: _sliderPosition, | |
); | |
} | |
Widget _drawLabels() { | |
int labelsToDisplay = widget.totalUnits ~/ 5 + 1; | |
List<Widget> labels = List.generate( | |
labelsToDisplay, | |
(idx) { | |
return Text( | |
"${widget.maxHeight - 5 * idx}", | |
style: labelsTextStyle, | |
); | |
}, | |
); | |
return Align( | |
alignment: Alignment.centerRight, | |
child: IgnorePointer( | |
child: Padding( | |
padding: EdgeInsets.only( | |
right: screenAwareSize(12.0, context), | |
bottom: marginBottomAdapted(context), | |
top: marginTopAdapted(context), | |
), | |
child: Column( | |
children: labels, | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
), | |
), | |
), | |
); | |
} | |
Widget _drawPersonImage() { | |
double personImageHeight = _sliderPosition + marginBottomAdapted(context); | |
return Align( | |
alignment: Alignment.bottomCenter, | |
child: SvgPicture.asset( | |
"images/person.svg", | |
height: personImageHeight, | |
width: personImageHeight / 3, | |
), | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:bmi_calculator/height/height_styles.dart'; | |
import 'package:bmi_calculator/widget_utils.dart'; | |
import 'package:flutter/material.dart'; | |
class HeightSlider extends StatelessWidget { | |
final int height; | |
const HeightSlider({Key key, this.height}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return IgnorePointer( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[ | |
SliderLabel(height: height), | |
Row( | |
children: <Widget>[ | |
SliderCircle(), | |
Expanded(child: SliderLine()), | |
], | |
), | |
], | |
), | |
); | |
} | |
} | |
class SliderLabel extends StatelessWidget { | |
final int height; | |
const SliderLabel({Key key, this.height}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Padding( | |
padding: EdgeInsets.only( | |
left: screenAwareSize(4.0, context), | |
bottom: screenAwareSize(2.0, context), | |
), | |
child: Text( | |
"$height", | |
style: TextStyle( | |
fontSize: selectedLabelFontSize, | |
color: Theme.of(context).primaryColor, | |
fontWeight: FontWeight.w600, | |
), | |
), | |
); | |
} | |
} | |
class SliderLine extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
mainAxisSize: MainAxisSize.max, | |
children: List.generate( | |
40, | |
(i) => Expanded( | |
child: Container( | |
height: 2.0, | |
decoration: BoxDecoration( | |
color: i.isEven | |
? Theme.of(context).primaryColor | |
: Colors.white), | |
), | |
)), | |
); | |
} | |
} | |
class SliderCircle extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
width: circleSizeAdapted(context), | |
height: circleSizeAdapted(context), | |
decoration: BoxDecoration( | |
color: Theme.of(context).primaryColor, | |
shape: BoxShape.circle, | |
), | |
child: Icon( | |
Icons.unfold_more, | |
color: Colors.white, | |
size: 0.6 * circleSizeAdapted(context), | |
), | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:bmi_calculator/widget_utils.dart'; | |
import 'package:flutter/material.dart'; | |
export 'package:bmi_calculator/styles.dart'; | |
double marginBottomAdapted(BuildContext context) => | |
screenAwareSize(marginBottom, context); | |
double marginTopAdapted(BuildContext context) => | |
screenAwareSize(marginTop, context); | |
double circleSizeAdapted(BuildContext context) => | |
screenAwareSize(circleSize, context); | |
const TextStyle labelsTextStyle = const TextStyle( | |
color: labelsGrey, | |
fontSize: labelsFontSize, | |
); | |
const double circleSize = 32.0; | |
const double marginBottom = 16.0; | |
const double marginTop = 26.0; | |
const double selectedLabelFontSize = 14.0; | |
const double labelsFontSize = 13.0; | |
const Color labelsGrey = const Color.fromRGBO(216, 217, 223, 1.0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment