From d81854a12facd860818f14a7e4f705bf7b864114 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 16 Jan 2024 13:30:58 +0300 Subject: [PATCH] Finished circumference charts --- lib/localization/ru/ru_translations.dart | 3 + .../bloc/my_profile_bloc.dart | 8 +- .../bloc/my_profile_state.dart | 14 +- .../my_profile_screen/my_profile_screen.dart | 279 +++++++++++++++--- 4 files changed, 262 insertions(+), 42 deletions(-) diff --git a/lib/localization/ru/ru_translations.dart b/lib/localization/ru/ru_translations.dart index f66e39c3..d7b2452c 100644 --- a/lib/localization/ru/ru_translations.dart +++ b/lib/localization/ru/ru_translations.dart @@ -108,6 +108,9 @@ final Map ru = { // Личный кабинет Screen "loading": "Загрузка...", + "weight_chart": "График веса", + "circumferences_chart": "График обхватов", + "bodymass_chart": "График ИМТ", "exit": "Выйти", "my_blog": "Мой блог", "lbl_01_04_21": "01.04.21", "lbl_01_05_21": "01.05.21", diff --git a/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart b/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart index 4bd82cf0..1d6d3277 100644 --- a/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart +++ b/lib/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart @@ -53,8 +53,10 @@ class MyProfileBloc extends Bloc { ); final data = jsonDecode(response.body); emit(state.copyWith( - user: data['user'], - chartPoints: data['chartPoints'], - userDetails: data['userDetails'])); + user: data['user'], + chartPoints: data['chartPoints'], + userDetails: data['userDetails'], + circumferences: data['circumferences'], + )); } } diff --git a/lib/presentation/profile/my_profile_screen/bloc/my_profile_state.dart b/lib/presentation/profile/my_profile_screen/bloc/my_profile_state.dart index 1232781a..5c6b88bb 100644 --- a/lib/presentation/profile/my_profile_screen/bloc/my_profile_state.dart +++ b/lib/presentation/profile/my_profile_screen/bloc/my_profile_state.dart @@ -5,24 +5,32 @@ part of 'my_profile_bloc.dart'; /// Represents the state of MyProfile in the application. class MyProfileState extends Equatable { MyProfileState( - {this.myProfileModelObj, this.user, this.userDetails, this.chartPoints}); + {this.myProfileModelObj, + this.user, + this.userDetails, + this.chartPoints, + this.circumferences}); MyProfileModel? myProfileModelObj; Map? user; Map? userDetails; Map? chartPoints; + Map? circumferences; @override - List get props => [myProfileModelObj, user, chartPoints]; + List get props => + [myProfileModelObj, user, chartPoints, circumferences]; MyProfileState copyWith( {MyProfileModel? myProfileModelObj, Map? user, Map? userDetails, - Map? chartPoints}) { + Map? chartPoints, + Map? circumferences}) { return MyProfileState( myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj, user: user ?? this.user, userDetails: userDetails ?? this.userDetails, + circumferences: circumferences ?? this.circumferences, chartPoints: chartPoints ?? this.chartPoints); } } diff --git a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart index 67cb3ad3..bd278215 100644 --- a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart +++ b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart @@ -40,17 +40,6 @@ class MyProfileScreen extends StatelessWidget { SizedBox(height: 15.v), _buildSettings(context), SizedBox(height: 30.v), - /*_buildChart( - context, - state.chartPoints?.values - .map((e) => e['weight']) - .toList() - .map((i) => i.toDouble()) - .toList() ?? - [0.0, 0.0, 0.0, 0.0], - state.chartPoints?.values - .map((e) => e['created_at']) - .toList()),*/ CarouselSlider( options: CarouselOptions(height: 370.0), items: [1, 2, 3].map((chartType) { @@ -58,10 +47,16 @@ class MyProfileScreen extends StatelessWidget { builder: (BuildContext context) { switch (chartType) { case 1: // График веса - return Container( - decoration: - BoxDecoration(color: Colors.white), - child: _buildChart( + return Column(children: [ + Text( + "weight_chart".tr, + style: CustomTextStyles.titleMediumBlack + .copyWith( + color: appTheme.black900, + ), + ), + SizedBox(height: 50.v), + _buildChart( context, state.chartPoints?.values .map((e) => e['weight']) @@ -72,29 +67,48 @@ class MyProfileScreen extends StatelessWidget { state.chartPoints?.values .map((e) => e['created_at']) .toList()), - ); + ]); case 2: // График ИМТ - return Container( - decoration: - BoxDecoration(color: Colors.white), - child: _buildChart( + return Column(children: [ + Text( + "bodymass_chart".tr, + style: CustomTextStyles.titleMediumBlack + .copyWith( + color: appTheme.black900, + ), + ), + SizedBox(height: 50.v), + _buildChart( context, _bodyMassIndex( - state.chartPoints!.values + state.chartPoints?.values .map((e) => e['weight']) .toList() .map((i) => i.toDouble()) - .toList(), - state.userDetails!['height'] - .toInt()) ?? - [0.0, 0.0, 0.0, 0.0], + .toList() ?? + [10.0, 20.0, 30.0, 40.0], + state.userDetails?['height'] + .toInt() ?? + 160), state.chartPoints?.values .map((e) => e['created_at']) .toList()), - ); - case 3: - return Text('text', - style: TextStyle(fontSize: 16.0)); + ]); + case 3: //График обхватов + return Column(children: [ + Text( + "circumferences_chart".tr, + style: CustomTextStyles.titleMediumBlack + .copyWith( + color: appTheme.black900, + ), + ), + SizedBox(height: 50.v), + _buildCircumferenceChart( + context, + state.circumferences, + ) + ]); default: return Text('text', style: TextStyle(fontSize: 16.0)); @@ -175,12 +189,6 @@ class MyProfileScreen extends StatelessWidget { /// Section Widget Widget _buildChart(BuildContext context, List chartPoints, dates) { - // Определение данных для графика - /* List chartData = chartPoints - .map((point) => - FlSpot(point['created_at'] as double, point['weight'] as double)) - .toList(); */ - // Определение осей графика List spots = []; for (int i = 0; i < 4; i++) { @@ -188,7 +196,6 @@ class MyProfileScreen extends StatelessWidget { FlSpot((i * 10 / 10) + 1, chartPoints[i]), ); } - print(spots); Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) { if (value % 1 != 0) { @@ -332,6 +339,206 @@ class MyProfileScreen extends StatelessWidget { ); } + Widget _buildCircumferenceChart(BuildContext context, chartPoints) { +// Определение осей графика + final chestCircumferences = + chartPoints.values.map((e) => e['chest_circumference']).toList(); + List spotsChest = []; + for (int i = 0; i < 4; i++) { + chestCircumferences[i] = chestCircumferences[i] * 10 / 10; + spotsChest.add( + FlSpot((i * 10 / 10) + 1, chestCircumferences[i] * 10 / 10), + ); + } + final waistCircumferences = + chartPoints.values.map((e) => e['waist_circumference']).toList(); + List spotsWaist = []; + for (int i = 0; i < 4; i++) { + waistCircumferences[i] = waistCircumferences[i] * 10 / 10; + spotsWaist.add( + FlSpot((i * 10 / 10) + 1, waistCircumferences[i] * 10 / 10), + ); + } + final hipCircumferences = + chartPoints.values.map((e) => e['hip_circumference']).toList(); + List spotsHip = []; + for (int i = 0; i < 4; i++) { + hipCircumferences[i] = hipCircumferences[i] * 10 / 10; + spotsHip.add( + FlSpot((i * 10 / 10) + 1, hipCircumferences[i]), + ); + } + + final allCircumferences = [ + ...chestCircumferences, + ...hipCircumferences, + ...waistCircumferences + ]; + + Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) { + if (value % 1 != 0) { + return Container(); + } + final style = TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: min(18, 18 * chartWidth / 300), + ); + return SideTitleWidget( + axisSide: meta.axisSide, + space: 16, + child: Text(meta.formattedValue, style: style), + ); + } + + Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) { + final style = TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + fontSize: min(18, 18 * chartWidth / 300), + ); + return SideTitleWidget( + axisSide: meta.axisSide, + space: 16, + child: Text(meta.formattedValue, style: style), + ); + } + + return Padding( + padding: const EdgeInsets.only( + left: 12, + bottom: 12, + right: 20, + top: 18, + ), + child: AspectRatio( + aspectRatio: 1, + child: LayoutBuilder( + builder: (context, constraints) { + return LineChart( + LineChartData( + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + maxContentWidth: 100, + tooltipBgColor: Colors.black, + getTooltipItems: (touchedSpots) { + return touchedSpots.map((LineBarSpot touchedSpot) { + final textStyle = TextStyle( + color: touchedSpot.bar.gradient?.colors[0] ?? + touchedSpot.bar.color, + fontWeight: FontWeight.bold, + fontSize: 14, + ); + return LineTooltipItem( + '${touchedSpot.x}, ${touchedSpot.y.toStringAsFixed(2)}', + textStyle, + ); + }).toList(); + }, + ), + handleBuiltInTouches: true, + getTouchLineStart: (data, index) => 0, + ), + lineBarsData: [ + LineChartBarData( + color: Colors.pink, + spots: spotsChest, + isCurved: true, + isStrokeCapRound: true, + barWidth: 3, + belowBarData: BarAreaData( + show: false, + ), + dotData: const FlDotData(show: false), + ), + LineChartBarData( + color: Colors.green, + spots: spotsWaist, + isCurved: true, + isStrokeCapRound: true, + barWidth: 3, + belowBarData: BarAreaData( + show: false, + ), + dotData: const FlDotData(show: false), + ), + LineChartBarData( + color: Colors.purple, + spots: spotsHip, + isCurved: true, + isStrokeCapRound: true, + barWidth: 3, + belowBarData: BarAreaData( + show: false, + ), + dotData: const FlDotData(show: false), + ), + ], + minY: allCircumferences.reduce( + (value, element) => value < element ? value : element), + maxY: allCircumferences.reduce( + (value, element) => value > element ? value : element), + minX: 1, + maxX: 4, + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) => + leftTitleWidgets(value, meta, constraints.maxWidth), + reservedSize: 56, + ), + drawBelowEverything: true, + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) => + bottomTitleWidgets(value, meta, constraints.maxWidth), + reservedSize: 36, + interval: 1, + ), + drawBelowEverything: true, + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: true, + horizontalInterval: 1.5, + verticalInterval: 5, + checkToShowHorizontalLine: (value) { + return value.toInt() == 0; + }, + getDrawingHorizontalLine: (_) => FlLine( + color: Colors.blue.withOpacity(1), + dashArray: [8, 2], + strokeWidth: 0.8, + ), + getDrawingVerticalLine: (_) => FlLine( + color: Colors.blue.withOpacity(1), + dashArray: [8, 2], + strokeWidth: 0.8, + ), + checkToShowVerticalLine: (value) { + return value.toInt() == 0; + }, + ), + borderData: FlBorderData(show: false), + ), + ); + }, + ), + ), + ); + } + /// Section Widget Widget _buildWidget(BuildContext context) { return Padding(