forked from eatvio/eatvio_app
Added pretty chart legend and date display for charts
This commit is contained in:
@@ -111,6 +111,9 @@ final Map<String, String> ru = {
|
||||
"weight_chart": "График веса",
|
||||
"circumferences_chart": "График обхватов",
|
||||
"bodymass_chart": "График ИМТ",
|
||||
"chest": "Грудь",
|
||||
"waist": "Талия",
|
||||
"hips": "Бедра",
|
||||
"exit": "Выйти",
|
||||
"active": "Активен",
|
||||
"archived": "В архиве",
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'dart:math';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
|
||||
class MyProfileScreen extends StatelessWidget {
|
||||
@@ -103,7 +104,94 @@ class MyProfileScreen extends StatelessWidget {
|
||||
case 3: //График обхватов
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showAddMeasurementDialog(context);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text("Добавить замер"),
|
||||
content: Column(
|
||||
children: [
|
||||
BlocSelector<
|
||||
MyProfileBloc,
|
||||
MyProfileState,
|
||||
TextEditingController?>(
|
||||
selector: (state) => state
|
||||
.chestCircumferenceController,
|
||||
builder: (context,
|
||||
chestCircumferenceController) {
|
||||
return TextField(
|
||||
controller:
|
||||
chestCircumferenceController,
|
||||
keyboardType:
|
||||
TextInputType
|
||||
.number,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
"chest_circumference"
|
||||
.tr),
|
||||
);
|
||||
}),
|
||||
BlocSelector<
|
||||
MyProfileBloc,
|
||||
MyProfileState,
|
||||
TextEditingController?>(
|
||||
selector: (state) => state
|
||||
.waistCircumferenceController,
|
||||
builder: (context,
|
||||
waistCircumferenceController) {
|
||||
return TextField(
|
||||
controller:
|
||||
waistCircumferenceController,
|
||||
keyboardType:
|
||||
TextInputType
|
||||
.number,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
"waist_circumference"
|
||||
.tr),
|
||||
);
|
||||
}),
|
||||
BlocSelector<
|
||||
MyProfileBloc,
|
||||
MyProfileState,
|
||||
TextEditingController?>(
|
||||
selector: (state) => state
|
||||
.hipCircumferenceController,
|
||||
builder: (context,
|
||||
hipCircumferenceController) {
|
||||
return TextField(
|
||||
controller:
|
||||
hipCircumferenceController,
|
||||
keyboardType:
|
||||
TextInputType
|
||||
.number,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
"hip_circumference"
|
||||
.tr),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Отмена"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Отправка данных на API
|
||||
// Закрытие диалогового окна
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Изменить"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Column(children: [
|
||||
Text(
|
||||
@@ -113,11 +201,15 @@ class MyProfileScreen extends StatelessWidget {
|
||||
color: appTheme.black900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50.v),
|
||||
SizedBox(height: 30.v),
|
||||
_buildCircumferenceChart(
|
||||
context,
|
||||
state.circumferences,
|
||||
)
|
||||
context,
|
||||
state.circumferences,
|
||||
state.chartPoints?.values
|
||||
.map<String>(
|
||||
(e) => e['created_at'])
|
||||
.toList()),
|
||||
_buildChartLegend()
|
||||
]));
|
||||
case 4:
|
||||
return NextStepButton(
|
||||
@@ -211,8 +303,41 @@ class MyProfileScreen extends StatelessWidget {
|
||||
return CustomBottomBar(onChanged: (BottomBarEnum type) {});
|
||||
}
|
||||
|
||||
Widget _buildChartLegend() {
|
||||
final colors = [Colors.pink, Colors.green, Colors.purple];
|
||||
final titles = ["chest".tr, "waist".tr, "hips".tr];
|
||||
final style = TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
for (var i = 0; i < colors.length; i++)
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: colors[i],
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(titles[i], style: style),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Section Widget
|
||||
Widget _buildChart(BuildContext context, List<double> chartPoints, dates) {
|
||||
Widget _buildChart(
|
||||
BuildContext context, List<double> chartPoints, List<String>? dates) {
|
||||
// Определение осей графика
|
||||
List<FlSpot> spots = [];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -221,20 +346,29 @@ class MyProfileScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
Widget bottomTitleWidgets(
|
||||
double value, TitleMeta meta, double chartWidth, List<String>? dates) {
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
final style = TextStyle(
|
||||
color: Colors.white,
|
||||
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),
|
||||
fontSize: min(13, 13 * chartWidth / 300),
|
||||
);
|
||||
int index = value.toInt();
|
||||
if (index >= 0 && index < dates!.length) {
|
||||
// Проверка на допустимый индекс в массиве dates
|
||||
String formattedDate =
|
||||
DateFormat('dd.MM').format(DateTime.parse(dates[index]));
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
space: 16,
|
||||
child: Text(formattedDate, style: style),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
@@ -320,8 +454,8 @@ class MyProfileScreen extends StatelessWidget {
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) =>
|
||||
bottomTitleWidgets(value, meta, constraints.maxWidth),
|
||||
getTitlesWidget: (value, meta) => bottomTitleWidgets(
|
||||
value, meta, constraints.maxWidth, dates),
|
||||
reservedSize: 36,
|
||||
interval: 1,
|
||||
),
|
||||
@@ -363,7 +497,8 @@ class MyProfileScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCircumferenceChart(BuildContext context, chartPoints) {
|
||||
Widget _buildCircumferenceChart(
|
||||
BuildContext context, chartPoints, List<String>? dates) {
|
||||
// Определение осей графика
|
||||
final chestCircumferences =
|
||||
chartPoints.values.map((e) => e['chest_circumference']).toList();
|
||||
@@ -399,20 +534,29 @@ class MyProfileScreen extends StatelessWidget {
|
||||
...waistCircumferences
|
||||
];
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
Widget bottomTitleWidgets(
|
||||
double value, TitleMeta meta, double chartWidth, List<String>? dates) {
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
final style = TextStyle(
|
||||
color: Colors.white,
|
||||
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),
|
||||
fontSize: min(13, 13 * chartWidth / 300),
|
||||
);
|
||||
int index = value.toInt();
|
||||
if (index >= 0 && index < dates!.length) {
|
||||
// Проверка на допустимый индекс в массиве dates
|
||||
String formattedDate =
|
||||
DateFormat('dd.MM').format(DateTime.parse(dates[index]));
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
space: 16,
|
||||
child: Text(formattedDate, style: style),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
@@ -520,8 +664,8 @@ class MyProfileScreen extends StatelessWidget {
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) =>
|
||||
bottomTitleWidgets(value, meta, constraints.maxWidth),
|
||||
getTitlesWidget: (value, meta) => bottomTitleWidgets(
|
||||
value, meta, constraints.maxWidth, dates),
|
||||
reservedSize: 36,
|
||||
interval: 1,
|
||||
),
|
||||
@@ -684,66 +828,5 @@ class MyProfileScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_showAddMeasurementDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text("Добавить замер"),
|
||||
content: Column(
|
||||
children: [
|
||||
BlocSelector<MyProfileBloc, MyProfileState, TextEditingController?>(
|
||||
selector: (state) => state.chestCircumferenceController,
|
||||
builder: (context, chestCircumferenceController) {
|
||||
return TextField(
|
||||
controller: chestCircumferenceController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration:
|
||||
InputDecoration(labelText: "chest_circumference".tr),
|
||||
);
|
||||
}),
|
||||
BlocSelector<MyProfileBloc, MyProfileState, TextEditingController?>(
|
||||
selector: (state) => state.waistCircumferenceController,
|
||||
builder: (context, waistCircumferenceController) {
|
||||
return TextField(
|
||||
controller: waistCircumferenceController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration:
|
||||
InputDecoration(labelText: "waist_circumference".tr),
|
||||
);
|
||||
}),
|
||||
BlocSelector<MyProfileBloc, MyProfileState, TextEditingController?>(
|
||||
selector: (state) => state.hipCircumferenceController,
|
||||
builder: (context, hipCircumferenceController) {
|
||||
return TextField(
|
||||
controller: hipCircumferenceController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration:
|
||||
InputDecoration(labelText: "hip_circumference".tr),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Отмена"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Отправка данных на API
|
||||
// Закрытие диалогового окна
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("Изменить"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_sendDataToApi(String chestCircumference, String waistCircumference,
|
||||
String hipCircumference) {}
|
||||
|
||||
Reference in New Issue
Block a user